Dari DMVs, dapatkah Anda memberi tahu jika koneksi menggunakan ApplicationIntent = ReadOnly?

23

Saya sudah menyiapkan Grup Ketersediaan Selalu, dan saya ingin memastikan pengguna saya menggunakan ApplicationIntent = ReadOnly di string koneksi mereka.

Dari SQL Server via DMVs (atau Extended Events atau apa pun), dapatkah saya memberi tahu jika pengguna terhubung dengan ApplicationIntent = ReadOnly di string koneksi mereka?

Tolong jangan jawab dengan cara MENCEGAH koneksi - bukan itu pertanyaan ini. Saya tidak bisa begitu saja menghentikan koneksi, karena kami memiliki aplikasi yang ada yang terhubung tanpa string yang benar, dan saya perlu tahu yang mana sehingga saya dapat bekerja dengan pengembang dan pengguna untuk memperbaikinya secara bertahap dari waktu ke waktu.

Asumsikan bahwa pengguna memiliki banyak aplikasi. Sebagai contoh, Bob terhubung dengan SQL Server Management Studio, dan dengan Excel. Dia terhubung dengan SSMS ketika dia perlu melakukan pembaruan, dan Excel ketika dia perlu membaca. Saya perlu memastikan dia menggunakan ApplicationIntent = ReadOnly ketika dia terhubung dengan Excel. (Itu bukan skenario yang tepat, tetapi cukup dekat untuk diilustrasikan.)

Brent Ozar
sumber
Saya pikir read-only ditentukan pada waktu routing TDS. Setelah dialihkan ke sekunder yang dapat dibaca, info tidak lagi diperlukan sehingga mungkin tidak masuk ke mesin.
Remus Rusanu
2
"read only routing first menghubungkan ke primary dan kemudian mencari sekunder yang dapat dibaca terbaik yang tersedia" tampaknya sekunder akan melihatnya sebagai koneksi biasa. Jika ada XEvent yang dipicu, itu akan menjadi yang utama. Saya tidak tahu apa yang saya bicarakan, tapi saya berspekulasi.
Remus Rusanu
1
@RemusRusanu yang Anda bicarakan sqlserver.read_only_route_completekarena dipicu hanya pada primer.
Kin Shah
@ Di situlah Anda pergi, persis seperti yang saya punya kode itu;)
Remus Rusanu
2
@RemusRusanu Saya bermain dengannya dan saya kira ini adalah yang terdekat yang bisa Anda dapatkan dengan gotcha - URL hanya baca sudah dikonfigurasi dengan benar & tidak ada masalah konektivitas. Di bawah kedua kasus itu, acara itu akan berhasil.
Kin Shah

Jawaban:

10

Mengambil pada sqlserver.read_only_route_completeExtended Event yang disebutkan oleh Kin dan Remus, ini adalah acara Debug yang bagus , tetapi tidak membawa banyak informasi dengannya - hanya route_port(mis. 1433) dan route_server_name(misalnya sqlserver-0.contoso.com) secara default . Ini juga hanya akan membantu menentukan kapan koneksi maksud baca-saja berhasil. Ada suatu read_only_route_failperistiwa tetapi saya tidak bisa memecatnya, mungkin jika ada masalah dengan URL perutean, sepertinya tidak menyala ketika instance sekunder tidak tersedia / shutdown sejauh yang saya tahu.

Namun saya memiliki beberapa keberhasilan bergabung yang dengan sqlserver.loginacara dan pelacakan kausalitas diaktifkan, bersama dengan beberapa tindakan (seperti sqlserver.username) untuk membuatnya berguna.

Langkah-langkah untuk Mereproduksi

Buat sesi Acara Diperpanjang untuk melacak acara yang relevan, ditambah tindakan yang berguna dan melacak hubungan sebab akibat:

CREATE EVENT SESSION [xe_watchLoginIntent] ON SERVER 
ADD EVENT sqlserver.login
    ( ACTION ( sqlserver.username ) ),
ADD EVENT sqlserver.read_only_route_complete
    ( ACTION ( 
        sqlserver.client_app_name,
        sqlserver.client_connection_id,
        sqlserver.client_hostname,
        sqlserver.client_pid,
        sqlserver.context_info,
        sqlserver.database_id,
        sqlserver.database_name,
        sqlserver.username 
        ) ),
ADD EVENT sqlserver.read_only_route_fail
    ( ACTION ( 
        sqlserver.client_app_name,
        sqlserver.client_connection_id,
        sqlserver.client_hostname,
        sqlserver.client_pid,
        sqlserver.context_info,
        sqlserver.database_id,
        sqlserver.database_name,
        sqlserver.username 
        ) )
ADD TARGET package0.event_file( SET filename = N'xe_watchLoginIntent' )
WITH ( 
    MAX_MEMORY = 4096 KB, 
    EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS, 
    MAX_DISPATCH_LATENCY = 30 SECONDS,
    MAX_EVENT_SIZE = 0 KB, 
    MEMORY_PARTITION_MODE = NONE, 
    TRACK_CAUSALITY = ON,   --<-- relate events
    STARTUP_STATE = ON      --<-- ensure sessions starts after failover
)

Jalankan sesi XE (pertimbangkan pengambilan sampel karena ini adalah acara Debug), dan kumpulkan beberapa info masuk:

koneksi sqlcmd

Catatan di sini sqlserver-0 adalah sekunder saya yang dapat dibaca dan sqlserver-1 primer. Di sini saya menggunakan -Ksaklar sqlcmduntuk mensimulasikan login maksud aplikasi read-only dan beberapa login SQL. Peristiwa baca-saja menyalakan login niat baca-saja yang berhasil.

Saat menjeda atau menghentikan sesi, saya dapat menanyakannya dan mencoba menghubungkan kedua peristiwa tersebut, misalnya:

DROP TABLE IF EXISTS #tmp

SELECT IDENTITY( INT, 1, 1 ) rowId, file_offset, CAST( event_data AS XML ) AS event_data
INTO #tmp
FROM sys.fn_xe_file_target_read_file( 'xe_watchLoginIntent*.xel', NULL, NULL, NULL )

ALTER TABLE #tmp ADD PRIMARY KEY ( rowId );
CREATE PRIMARY XML INDEX _pxmlidx_tmp ON #tmp ( event_data );


-- Pair up the login and read_only_route_complete events via xxx
DROP TABLE IF EXISTS #users

SELECT
    rowId,
    event_data.value('(event/@timestamp)[1]', 'DATETIME2' ) AS [timestamp],
    event_data.value('(event/action[@name="username"]/value/text())[1]', 'VARCHAR(100)' ) AS username,
    event_data.value('(event/action[@name="attach_activity_id_xfer"]/value/text())[1]', 'VARCHAR(100)' ) AS attach_activity_id_xfer,
    event_data.value('(event/action[@name="attach_activity_id"]/value/text())[1]', 'VARCHAR(100)' ) AS attach_activity_id
INTO #users
FROM #tmp l
WHERE l.event_data.exist('event[@name="login"]') = 1
  AND l.event_data.exist('(event/action[@name="username"]/value/text())[. = "SqlUserShouldBeReadOnly"]') = 1


DROP TABLE IF EXISTS #readonly

SELECT *,
    event_data.value('(event/@timestamp)[1]', 'DATETIME2' ) AS [timestamp],
    event_data.value('(event/data[@name="route_port"]/value/text())[1]', 'INT' ) AS route_port,
    event_data.value('(event/data[@name="route_server_name"]/value/text())[1]', 'VARCHAR(100)' ) AS route_server_name,
    event_data.value('(event/action[@name="username"]/value/text())[1]', 'VARCHAR(100)' ) AS username,
    event_data.value('(event/action[@name="client_app_name"]/value/text())[1]', 'VARCHAR(100)' ) AS client_app_name,
    event_data.value('(event/action[@name="attach_activity_id_xfer"]/value/text())[1]', 'VARCHAR(100)' ) AS attach_activity_id_xfer,
    event_data.value('(event/action[@name="attach_activity_id"]/value/text())[1]', 'VARCHAR(100)' ) AS attach_activity_id
INTO #readonly
FROM #tmp
WHERE event_data.exist('event[@name="read_only_route_complete"]') = 1


SELECT *
FROM #users u
    LEFT JOIN #readonly r ON u.attach_activity_id_xfer = r.attach_activity_id_xfer

SELECT u.username, COUNT(*) AS logins, COUNT( DISTINCT r.rowId ) AS records
FROM #users u
    LEFT JOIN #readonly r ON u.attach_activity_id_xfer = r.attach_activity_id_xfer
GROUP BY u.username

Kueri harus menampilkan info masuk dengan dan tanpa maksud hanya-baca aplikasi:

Hasil Permintaan

  • read_only_route_completeadalah acara Debug jadi gunakan dengan hemat. Pertimbangkan contoh pengambilan sampel.
  • dua acara bersama dengan track kausalitas menawarkan potensi untuk memenuhi kebutuhan Anda - pengujian lebih lanjut diperlukan pada rig sederhana ini
  • Saya memang memperhatikan jika nama database tidak ditentukan dalam koneksi, hal-hal sepertinya tidak berfungsi
  • Saya berusaha mendapatkan pair_matchingtarget untuk bekerja tetapi kehabisan waktu. Ada beberapa potensi untuk pengembangan di sini, seperti:

    ALTER EVENT SESSION [xe_watchLoginIntent] ON SERVER
    ADD TARGET package0.pair_matching ( 
        SET begin_event = N'sqlserver.login',
            begin_matching_actions = N'sqlserver.username',
            end_event = N'sqlserver.read_only_route_complete',
            end_matching_actions = N'sqlserver.username'
        )
wBob
sumber
5

Tidak, tampaknya tidak ada properti koneksi terbuka DMV (baik di sys.dm_exec_connections atau sys.dm_exec_sessions ) atau bahkan CONNECTIONPROPERTY yang berkaitan dengan ApplicationIntentkata kunci ConnectionString.

Namun, mungkin layak meminta, melalui Microsoft Connect, bahwa properti ini ditambahkan ke sys.dm_exec_connectionsDMV karena tampaknya merupakan properti koneksi yang disimpan di suatu tempat dalam memori SQL Server, berdasarkan info berikut yang ditemukan di halaman MSDN untuk Dukungan SqlClient untuk Ketersediaan Tinggi, Pemulihan Bencana (cetak miring yang dicetak miring):

Menentukan Maksud Aplikasi

Ketika ApplicationIntent = ReadOnly , klien meminta beban kerja baca saat menghubungkan ke database yang diaktifkan AlwaysOn. Server akan menegakkan maksud pada waktu koneksi dan selama pernyataan database USE tetapi hanya untuk database yang diaktifkan Selalu Aktif.

Jika USEpernyataan dapat diverifikasi, maka ApplicationIntentperlu ada di luar upaya koneksi awal. Namun, saya belum memverifikasi perilaku ini secara pribadi.


PS Saya telah berpikir bahwa kita dapat menggunakan fakta bahwa:

  • Replika Utama dapat disetel untuk melarang akses ReadOnly ke satu atau lebih Database, dan
  • "niat" akan ditegakkan ketika sebuah USEpernyataan dieksekusi.

Idenya adalah untuk membuat Database baru semata-mata dengan tujuan untuk menguji dan melacak pengaturan ini. DB baru akan digunakan dalam Grup Ketersediaan baru yang akan ditetapkan hanya mengizinkan READ_WRITEkoneksi. Teorinya adalah bahwa di dalam Logon Trigger, di EXEC(N'USE [ReadWriteOnly]; INSERT INTO LogTable...;');dalam TRY...CATCHkonstruk, dengan dasarnya tidak ada di CATCHblok, tidak akan menghasilkan kesalahan untuk koneksi ReadWrite (yang akan masuk sendiri dalam DB baru), atau USEkesalahan akan pada koneksi ReadOnly, tetapi maka tidak ada yang akan terjadi karena kesalahan ditangkap dan diabaikan (dan INSERTpernyataan itu tidak akan pernah tercapai). Dalam kedua kasus tersebut, peristiwa Logon aktual tidak akan dicegah / ditolak. Kode Pemicu Logon secara efektif adalah:

BEGIN TRY
    EXEC(N'
        USE [ApplicationIntentTracking];
        INSERT INTO dbo.ReadWriteLog (column_list)
          SELECT sess.some_columns, conn.other_columns
          FROM   sys.dm_exec_connections conn
          INNER JOIN sys.dm_exec_sessions sess
                  ON sess.[session_id] = conn.[session_id]
          WHERE   conn.[session_id] = @@SPID;
        ');
END TRY
BEGIN CATCH
    DECLARE @DoNothing INT;
END CATCH;

Sayangnya, ketika menguji efek mengeluarkan USEpernyataan di EXEC()dalam bagian TRY...CATCHdalam Transaksi, saya menemukan bahwa pelanggaran akses adalah pengguguran tingkat batch, bukan aborsi tingkat pernyataan. Dan pengaturan XACT_ABORT OFFtidak mengubah apa pun. Saya bahkan membuat Prosedur Disimpan SQLCLR sederhana untuk digunakan Context Connection = true;dan kemudian dipanggil SqlConnection.ChangeDatabase()dalam a try...catchdan Transaksi masih dibatalkan. Dan Anda tidak dapat menggunakan Enlist=falsepada Context Connection. Dan menggunakan koneksi reguler / eksternal di SQLCLR untuk melangkah keluar dari Transaksi tidak akan membantu karena itu akan menjadi Koneksi yang sama sekali baru.

Ada kemungkinan yang sangat, sangat tipis bahwa HAS_DBACCESS dapat digunakan sebagai pengganti USEpernyataan, tetapi saya benar-benar tidak memiliki harapan tinggi untuk dapat menggabungkan informasi Koneksi saat ini ke dalam ceknya. Tapi saya juga tidak punya cara untuk mengujinya.

Tentu saja, jika ada Bendera Jejak yang dapat menyebabkan pelanggaran akses tidak dibatalkan secara batch, maka rencana yang disebutkan di atas harus bekerja ;-).

Solomon Rutzky
sumber
Sayangnya, saya tidak dapat menyangkal mereka - replika yang dapat dibaca lainnya bisa turun. Saya masih membutuhkan kueri baca untuk bekerja pada primer - Saya hanya perlu tahu kapan itu terjadi.
Brent Ozar
@BrentOzar Saya telah memperbarui jawaban saya untuk memasukkan Langkah 3 baru yang akan memeriksa kondisi itu dan jika tidak ada Sekunder yang tersedia, maka itu akan memungkinkan Koneksi. Juga, jika tujuannya masih untuk hanya "tahu kapan kamu terjadi", maka pengaturan yang sama dapat digunakan, cukup ubah ROLLBACKdalam Pemicu Login INSERTke log tabel :-)
Solomon Rutzky
1
ini adalah jawaban yang bagus, tetapi ini bukan untuk pertanyaan ini. Saya tidak perlu menghentikan pengguna, saya perlu memonitor ketika itu terjadi. Kami memiliki aplikasi yang sudah ada yang perlu kami tandai dan perbaiki secara bertahap. Jika saya menghentikan pengguna untuk masuk, itu akan menyebabkan pemberontakan langsung. Jika Anda ingin membuat pertanyaan terpisah untuk ini, dan memposting jawaban Anda di sana, itu bagus - tapi tolong fokuskan jawaban Anda di sini pada pertanyaan saya yang sebenarnya. Terima kasih.
Brent Ozar
@BrentOzar Maaf, saya salah mengerti komentar Anda kepada Tom sebagai sesuatu yang sedikit lebih kuat dari sekadar melacak / masuk. Saya telah menghapus bagian dari jawaban saya yang berhubungan dengan mencegah akses.
Solomon Rutzky
@ BrentOzar Saya menambahkan beberapa catatan di bawah garis (di bagian PS) yang hampir menjadi solusi, tetapi gagal di akhir. Saya memposting catatan-catatan itu kalau-kalau itu memicu ide dalam diri Anda (atau orang lain) untuk menemukan bagian yang hilang, atau bahkan sesuatu yang sama sekali berbeda, yang mungkin memecahkan teka-teki ini.
Solomon Rutzky
2

Anda ingin menjadi seberapa sakit? Aliran TDS tidak terlalu sulit untuk diproksi, kami melakukannya untuk aplikasi SaaS kami. Bit yang Anda cari (secara harfiah sedikit) ada di pesan login7. Anda bisa membuat pengguna Anda terhubung melalui proxy dan login / menegakkan bit di sana. Sial, Anda bahkan bisa menyalakannya untuk mereka. :)

Walden Leverich
sumber
Jelas lebih sakit daripada yang saya inginkan, tapi terima kasih, hahaha.
Brent Ozar
-1

Apakah aplikasi Anda menggunakan akun layanan atau mungkin beberapa akun layanan? Jika demikian, gunakan Peristiwa Diperpanjang untuk memantau lalu lintas login Anda tetapi mengecualikan akun layanan Anda di server utama selalu aktif. Anda sekarang harus dapat melihat siapa yang masuk ke server selalu aktif primer dan tidak menggunakan string koneksi sekunder readonly. Saya bersiap untuk menginstal Always-On dan inilah yang akan saya lakukan kecuali Anda memberi tahu saya ini tidak akan berhasil.

ArmorDba
sumber
1
Tom - asumsikan bahwa pengguna memiliki banyak aplikasi. Misalnya, Bob terhubung dengan SQL Server Management Studio, dan dengan Excel. Dia terhubung dengan SSMS ketika dia perlu melakukan pembaruan, dan Excel ketika dia perlu membaca. Saya perlu memastikan dia menggunakan ApplicationIntent = ReadOnly ketika dia terhubung dengan Excel. (Itu bukan skenario yang tepat, tetapi cukup dekat untuk diilustrasikan.)
Brent Ozar
Saya juga memiliki orang-orang yang terhubung ke server produksi saya dengan Excel dengan akses yang sangat terbatas. Mereka terhubung dengan hak-hak mereka. Saya harap saya bisa melihat mereka. Kami akan segera membawa Selalu Aktif kami.
ArmorDba
-1

Sayangnya saya tidak memiliki lingkungan untuk menguji yang berikut ini, dan tidak diragukan lagi ada beberapa poin di mana ia mungkin gagal, tetapi saya akan membuangnya di sana untuk apa nilainya.

Prosedur tersimpan CLR memiliki akses ke koneksi saat ini melalui new SqlConnection("context connection=true")konstruk (diambil dari sini ). Jenis SqlConnection memperlihatkan properti ConnectionString . Karena ApplicationIntent ada dalam string koneksi awal saya kira itu akan tersedia di properti ini dan dapat diuraikan. Ada banyak hand-off dalam rantai itu, tentu saja, begitu banyak peluang untuk semuanya menjadi berbentuk buah pir.

Ini akan dijalankan dari Pemicu Logon dan nilai-nilai yang diperlukan bertahan sesuai kebutuhan.

Michael Green
sumber
1
Ini tidak akan berhasil. Kode SQLCLR tidak memiliki akses ke Koneksi saat ini, ia memiliki akses ke Sesi saat ini melalui Koneksi Konteks. Objek SqlConnection dalam kode .NET tidak memanfaatkan koneksi aktual yang dibuat dari perangkat lunak klien asli ke dalam SQL Server. Itu adalah dua hal yang terpisah.
Solomon Rutzky
Oh well, sudahlah.
Michael Green
Tidak, ini tidak berhasil.
Brent Ozar