จาก DMV คุณสามารถบอกได้หรือไม่ว่าการเชื่อมต่อนั้นใช้ ApplicationIntent = ReadOnly หรือไม่


23

ฉันมีการตั้งค่ากลุ่มความพร้อมใช้งานตลอดเวลาและฉันต้องการตรวจสอบให้แน่ใจว่าผู้ใช้ของฉันกำลังใช้ ApplicationIntent = อ่านได้อย่างเดียวในสตริงการเชื่อมต่อของพวกเขา

จาก SQL Server ผ่าน DMVs (หรือ Extended Events หรืออะไรก็ตาม) ฉันสามารถบอกได้ไหมว่าผู้ใช้เชื่อมต่อกับ ApplicationIntent = อ่านได้อย่างเดียวในสตริงการเชื่อมต่อของพวกเขาหรือไม่

โปรดอย่าตอบด้วยวิธีป้องกันการเชื่อมต่อ - นั่นไม่ใช่สิ่งที่คำถามนี้เกี่ยวกับ ฉันไม่สามารถหยุดการเชื่อมต่อได้เพียงเพราะเรามีแอปพลิเคชั่นที่มีอยู่ซึ่งกำลังเชื่อมต่อโดยไม่มีสายอักขระที่ถูกต้องและฉันจำเป็นต้องรู้ว่าพวกเขาเป็นใครฉันจึงสามารถทำงานร่วมกับนักพัฒนาและผู้ใช้

สมมติว่าผู้ใช้มีหลายแอปพลิเคชัน ตัวอย่างเช่น Bob เชื่อมต่อกับ SQL Server Management Studio และกับ Excel เขาเชื่อมต่อกับ SSMS เมื่อต้องการอัปเดตและ Excel เมื่อเขาต้องการอ่าน ฉันต้องแน่ใจว่าเขาใช้ ApplicationIntent = อ่านอย่างเดียวเมื่อเขาเชื่อมต่อกับ Excel (นั่นไม่ใช่สถานการณ์ที่แน่นอน แต่ใกล้พอที่จะอธิบาย)


ฉันคิดว่าการอ่านอย่างเดียวจะถูกกำหนดในเวลาการจัดเส้นทาง TDS เมื่อถูกส่งไปยังรองที่อ่านได้แล้วข้อมูลก็ไม่จำเป็นอีกต่อไปดังนั้นมันอาจไม่ได้ทำให้มันกลายเป็นเครื่องยนต์
Remus Rusanu

2
"อ่านอย่างเดียวการกำหนดเส้นทางก่อนเชื่อมต่อกับหลักแล้วมองหารองที่อ่านได้ดีที่สุด"ปรากฏว่ารองจะเห็นว่าเป็นการเชื่อมต่อปกติ หากมี XEvent ใด ๆ ที่ถูกเรียกมันจะอยู่ในหลัก ฉันไม่รู้ว่าฉันกำลังพูดถึงอะไร แต่ฉันคาดเดา
Remus Rusanu

1
@RemusRusanu คุณกำลังพูดถึงsqlserver.read_only_route_completeเพราะมันถูกเรียกใช้ในหลักเท่านั้น
Kin Shah

@Kin ที่นั่นคุณไปอย่างที่ฉันจะได้รหัส;)
รีมัส Rusanu

2
@RemusRusanu ฉันเล่นกับมันและฉันเดาว่ามันใกล้เคียงที่สุดที่คุณจะได้รับด้วย gotchas - URL แบบอ่านอย่างเดียวมีการกำหนดค่าอย่างถูกต้อง & ไม่มีปัญหาการเชื่อมต่อ ภายใต้ทั้งสองกรณีเหตุการณ์นั้นจะสำเร็จ
Kin Shah

คำตอบ:


10

การเลือกใช้งานsqlserver.read_only_route_completeExtended Event ที่ Kin และ Remus พูดถึงนั้นเป็นDebug ที่ดีแต่มันไม่ได้มีข้อมูลมากมาย - route_portเช่น (เช่น 1433) และroute_server_name(เช่น sqlserver-0.contoso.com) . สิ่งนี้จะช่วยในการพิจารณาว่าการเชื่อมต่อแบบอ่านอย่างเดียวนั้นสำเร็จเมื่อใด มีread_only_route_failเหตุการณ์ แต่ฉันไม่สามารถเริ่มต้นได้บางทีอาจมีปัญหากับ URL การกำหนดเส้นทางดูเหมือนว่าจะไม่เริ่มทำงานเมื่ออินสแตนซ์รองไม่พร้อมใช้งาน / ปิดเครื่องเท่าที่ฉันสามารถบอกได้

อย่างไรก็ตามฉันประสบความสำเร็จในการเข้าร่วมที่sqlserver.loginเปิดใช้งานเหตุการณ์และการติดตามเวรกรรมพร้อมกับการกระทำบางอย่าง (เช่นsqlserver.username) เพื่อให้มีประโยชน์

ขั้นตอนในการทำซ้ำ

สร้างเซสชันเพิ่มเติมเพื่อติดตามกิจกรรมที่เกี่ยวข้องรวมถึงการกระทำที่เป็นประโยชน์และติดตามสาเหตุ:

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
)

เรียกใช้เซสชัน XE (พิจารณาการสุ่มตัวอย่างเนื่องจากนี่เป็นเหตุการณ์ดีบั๊ก) และรวบรวมการเข้าสู่ระบบบางอย่าง:

การเชื่อมต่อ sqlcmd

หมายเหตุนี่ sqlserver-0 เป็นของฉันรองอ่านและ sqlserver-1 หลัก ที่นี่ฉันใช้-Kสวิตช์ของsqlcmdเพื่อจำลองการเข้าสู่ระบบความตั้งใจของแอปพลิเคชันแบบอ่านอย่างเดียวและการเข้าสู่ระบบ SQL บางส่วน เหตุการณ์แบบอ่านอย่างเดียวจะเริ่มต้นด้วยการเข้าสู่ระบบแบบอ่านอย่างเดียวที่ประสบความสำเร็จ

เมื่อหยุดชั่วคราวหรือหยุดเซสชันฉันสามารถสอบถามและพยายามเชื่อมโยงสองเหตุการณ์เช่น:

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

แบบสอบถามควรแสดงการเข้าสู่ระบบโดยมีและไม่มีเจตนาอ่านอย่างเดียวของแอปพลิเคชัน:

ผลลัพธ์การค้นหา

  • read_only_route_completeเป็นกิจกรรมดีบั๊กดังนั้นควรใช้อย่างประหยัด พิจารณาการสุ่มตัวอย่างเช่น
  • ทั้งสองเหตุการณ์พร้อมกับการติดตามสาเหตุเสนอศักยภาพที่จะตอบสนองความต้องการของคุณ - การทดสอบเพิ่มเติมที่จำเป็นบนแท่นขุดเจาะง่ายนี้
  • ฉันสังเกตว่าชื่อฐานข้อมูลไม่ได้ระบุในการเชื่อมต่อสิ่งต่าง ๆ ไม่ทำงาน
  • ฉันพยายามหาpair_matchingเป้าหมายให้ทำงาน แต่หมดเวลา มีศักยภาพในการพัฒนาที่นี่บางอย่างเช่น:

    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'
        )
    

5

ไม่ไม่ปรากฏว่ามีคุณสมบัติการเชื่อมต่อที่สัมผัสกับ DMV (ในsys.dm_exec_connectionsหรือsys.dm_exec_sessions ) หรือCONNECTIONPROPERTYที่เกี่ยวข้องกับApplicationIntentคำสำคัญ ConnectionString

อย่างไรก็ตามมันอาจคุ้มค่าที่จะขอผ่าน Microsoft Connect ซึ่งคุณสมบัตินี้จะถูกเพิ่มในsys.dm_exec_connectionsDMV เนื่องจากดูเหมือนว่าจะเป็นคุณสมบัติของการเชื่อมต่อที่เก็บไว้ที่ใดที่หนึ่งในหน่วยความจำของ SQL Server ตามข้อมูลต่อไปนี้ที่พบในหน้า MSDN สำหรับสนับสนุน SqlClient สำหรับความพร้อมใช้งานสูง, การกู้คืนความเสียหาย (เน้นที่ตัวเอียง)

การระบุเจตนาของแอปพลิเคชัน

เมื่อApplicationIntent = ReadOnlyไคลเอนต์ร้องขอปริมาณงานที่อ่านเมื่อเชื่อมต่อกับฐานข้อมูลที่เปิดใช้งาน AlwaysOn เซิร์ฟเวอร์จะบังคับใช้เจตจำนงในเวลาเชื่อมต่อและระหว่างคำสั่งฐานข้อมูล USEแต่เฉพาะกับฐานข้อมูลที่เปิดใช้งานเสมอ

หากUSEคำสั่งสามารถตรวจสอบได้แล้วApplicationIntentจะต้องมีอยู่นอกเหนือความพยายามเชื่อมต่อเริ่มต้น อย่างไรก็ตามฉันยังไม่ได้ตรวจสอบพฤติกรรมนี้เป็นการส่วนตัว


ป.ล. ฉันคิดว่าเราสามารถใช้ประโยชน์จากข้อเท็จจริงที่:

  • Replica หลักสามารถตั้งค่าให้ไม่อนุญาตให้ ReadOnly เข้าถึงฐานข้อมูลหนึ่งฐานข้อมูลและ
  • "เจตนา" จะถูกบังคับใช้เมื่อUSEคำสั่งถูกดำเนินการ

แนวคิดนี้คือการสร้างฐานข้อมูลใหม่เพื่อใช้ในการทดสอบและติดตามการตั้งค่านี้เท่านั้น ฐานข้อมูลใหม่จะถูกใช้ในกลุ่มความพร้อมใช้งานใหม่ที่จะตั้งค่าให้อนุญาตREAD_WRITEการเชื่อมต่อเท่านั้น ทฤษฎีคือว่าภายใน Logon Trigger EXEC(N'USE [ReadWriteOnly]; INSERT INTO LogTable...;');ภายในTRY...CATCHโครงสร้างโดยไม่มีอะไรในCATCHบล็อกจะสร้างข้อผิดพลาดสำหรับการเชื่อมต่อ ReadWrite (ซึ่งจะบันทึกตัวเองในฐานข้อมูลใหม่) หรือUSEข้อผิดพลาดในการเชื่อมต่อ ReadOnly แต่ แล้วไม่มีอะไรที่จะเกิดขึ้นเนื่องจากข้อผิดพลาดจะถูกจับได้และถูกละเลย (และINSERTคำสั่งจะไม่ได้ถึง) ไม่ว่าในกรณีใดเหตุการณ์การเข้าสู่ระบบจริงจะไม่ถูกป้องกัน / ปฏิเสธ รหัสทริกเกอร์การเข้าสู่ระบบจะมีประสิทธิภาพ:

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;

น่าเสียดายที่เมื่อทดสอบผลของการออกUSEคำสั่งภายในEXEC()ภายในTRY...CATCHของธุรกรรมฉันพบว่าการละเมิดการเข้าถึงเป็นการยกเลิกระดับแบทช์ไม่ใช่การยกเลิกระดับคำสั่ง และการตั้งค่าXACT_ABORT OFFไม่ได้เปลี่ยนแปลงอะไรเลย ฉันได้สร้าง SQLCLR Stored Procedure เพื่อใช้งานContext Connection = true;แล้วเรียกว่าSqlConnection.ChangeDatabase()ภายใน a try...catchและธุรกรรมยังคงถูกยกเลิก และคุณไม่สามารถใช้Enlist=falseกับการเชื่อมต่อบริบท และการใช้การเชื่อมต่อปกติ / ภายนอกใน SQLCLR ไปยังขั้นตอนนอกการทำธุรกรรมจะไม่ช่วยเหมือนเป็นการเชื่อมต่อใหม่ทั้งหมด

มีความเป็นไปได้น้อยมากที่HAS_DBACCESSสามารถใช้แทนUSEคำสั่งได้ แต่ฉันไม่มีความหวังสูงมากที่จะสามารถรวมข้อมูลการเชื่อมต่อปัจจุบันลงในเช็คได้ แต่ฉันก็ไม่มีวิธีทดสอบเช่นกัน

แน่นอนหากมีการตั้งค่าสถานะการสืบค้นกลับที่สามารถทำให้เกิดการละเมิดการเข้าถึงที่จะไม่ยกเลิกชุดงานแล้วแผนดังกล่าวข้างต้นควรทำงาน ;-)


ขออภัยฉันไม่สามารถปฏิเสธได้ - แบบจำลองอื่นที่อ่านได้อาจไม่ทำงาน ฉันยังต้องการอ่านแบบสอบถามเพื่อทำงานในหลัก - ฉันต้องรู้ว่าเมื่อพวกเขากำลังเกิดขึ้น
Brent Ozar

@BrentOzar ฉันได้อัปเดตคำตอบของฉันเพื่อรวมขั้นตอนที่ 3 ใหม่ที่จะตรวจสอบเงื่อนไขนั้นและหากไม่มี Secondaries ที่มีอยู่ก็จะอนุญาตการเชื่อมต่อ นอกจากนี้หากความตั้งใจยังคงเป็นเพียง "รู้ว่าเกิดอะไรขึ้น" คุณสามารถใช้การตั้งค่าเดียวกันได้เพียงเปลี่ยนROLLBACKการเข้าสู่ระบบทริกเกอร์INSERTเป็นตารางบันทึก :-)
โซโลมอน Rutzky

1
นี่เป็นคำตอบที่ดี แต่ไม่ใช่สำหรับคำถามนี้ ฉันไม่ต้องการหยุดผู้ใช้ฉันต้องตรวจสอบเมื่อมันเกิดขึ้น เรามีแอพที่มีอยู่ที่เราต้องระบุและแก้ไขอย่างค่อยเป็นค่อยไป หากฉันหยุดผู้ใช้ไม่ให้เข้าสู่ระบบก็จะทำให้เกิดการประท้วงทันที หากคุณต้องการสร้างคำถามแยกต่างหากสำหรับสิ่งนี้และโพสต์คำตอบของคุณที่นั่นนั่นจะดีมาก - แต่โปรดเน้นคำตอบของคุณที่นี่กับคำถามจริงของฉัน ขอบคุณ
Brent Ozar

@BrentOzar ขออภัยฉันเข้าใจผิดความคิดเห็นของคุณต่อ Tom ว่าหมายถึงบางสิ่งที่แข็งแกร่งกว่าการติดตาม / การบันทึก ฉันได้ลบบางส่วนของคำตอบที่จัดการกับการป้องกันการเข้าถึง
โซโลมอน Rutzky

@BrentOzar ฉันเพิ่มบันทึกย่อใต้บรรทัด (ในส่วน PS) ที่ใกล้เคียงกับการแก้ปัญหา แต่ถูกขัดขวางในตอนท้าย ฉันโพสต์บันทึกย่อเหล่านั้นในกรณีที่มันจุดประกายความคิดในตัวคุณ (หรือคนอื่น) เพื่อให้ชิ้นส่วนที่ขาดหายไปหรือแม้แต่สิ่งที่แตกต่างอย่างสิ้นเชิงที่อาจแก้ไขปริศนานี้
โซโลมอน Rutzky

2

คุณอยากเป็นอย่างไร สตรีม TDS นั้นไม่ยากสำหรับพร็อกซีเราทำเพื่อแอพ SaaS ของเรา บิตที่คุณกำลังมองหา (อักษรบิต) อยู่ในข้อความ login7 คุณสามารถให้ผู้ใช้ของคุณเชื่อมต่อผ่านพร็อกซีและเข้าสู่ระบบ / บังคับใช้บิตที่นั่น นรกคุณสามารถเปิดมันเพื่อพวกเขาได้ :)


มันป่วยหนักกว่าที่ฉันต้องการ แต่ขอบคุณฮ่าฮ่าฮ่า
Brent Ozar

-1

แอปพลิเคชันของคุณใช้บัญชีบริการหรืออาจเป็นบัญชีบริการหลายบัญชีหรือไม่ ถ้าเป็นเช่นนั้นใช้ Extended Event เพื่อตรวจสอบปริมาณการใช้งานเข้าสู่ระบบของคุณ แต่ไม่รวมบัญชีบริการของคุณบนเซิร์ฟเวอร์ always-on หลักของคุณ ตอนนี้คุณควรจะสามารถดูว่าใครเข้าสู่เซิร์ฟเวอร์ always-on หลักและไม่ใช้สตริงการเชื่อมต่อรองแบบอ่านอย่างเดียว ฉันกำลังเตรียมพร้อมที่จะติดตั้ง Always-On และนี่คือสิ่งที่ฉันจะทำเว้นแต่คุณจะบอกฉันว่าสิ่งนี้จะไม่ทำงาน


1
ทอม - สมมติว่าผู้ใช้มีหลายแอปพลิเคชัน ตัวอย่างเช่น Bob เชื่อมต่อกับ SQL Server Management Studio และกับ Excel เขาเชื่อมต่อกับ SSMS เมื่อต้องการอัปเดตและ Excel เมื่อเขาต้องการอ่าน ฉันต้องแน่ใจว่าเขาใช้ ApplicationIntent = อ่านอย่างเดียวเมื่อเขาเชื่อมต่อกับ Excel (นั่นไม่ใช่สถานการณ์ที่แน่นอน แต่ใกล้พอที่จะอธิบาย)
Brent Ozar

ฉันยังมีคนที่เชื่อมต่อกับเซิร์ฟเวอร์ที่ใช้งานจริงของฉันด้วย Excel ที่มีการเข้าถึงที่ จำกัด มาก พวกเขาเชื่อมต่อกับสิทธิของพวกเขา ฉันหวังว่าฉันจะสามารถเห็นพวกเขา เราจะเปิดตัว Always On ของเราในไม่ช้า
ArmorDba

-1

น่าเสียดายที่ฉันไม่มีสภาพแวดล้อมในการทดสอบสิ่งต่อไปนี้และมีหลายจุดที่มันอาจจะล้มเหลวได้อย่างไม่ต้องสงสัย แต่ฉันจะทิ้งมันลงไปในสิ่งที่คุ้มค่า

กระบวนงานที่เก็บไว้ CLR มีการเข้าถึงการเชื่อมต่อปัจจุบันผ่านการnew SqlConnection("context connection=true")สร้าง (นำมาจากที่นี่ ) ประเภท SqlConnection เปิดเผยคุณสมบัติConnectionString เนื่องจาก ApplicationIntent อยู่ในสตริงการเชื่อมต่อเริ่มต้นฉันคิดว่ามันจะมีอยู่ในคุณสมบัตินี้และสามารถแยกวิเคราะห์ได้ มีการคัดมือในโซ่นั้นมากมายแน่นอนว่ามีโอกาสมากมายสำหรับทุกคนที่จะไปเป็นรูปลูกแพร์

สิ่งนี้จะเรียกใช้จากLogon Triggerและค่าที่ต้องการจะคงอยู่ตามต้องการ


1
สิ่งนี้จะไม่ทำงาน รหัส SQLCLR ไม่สามารถเข้าถึงการเชื่อมต่อปัจจุบันได้ แต่สามารถเข้าถึงเซสชันปัจจุบันผ่านการเชื่อมต่อบริบท วัตถุ SqlConnection ในรหัส. NET ไม่แตะในการเชื่อมต่อที่เกิดขึ้นจริงจากซอฟต์แวร์ไคลเอนต์ต้นฉบับลงใน SQL Server นั่นคือสองสิ่งที่แยกจากกัน
โซโลมอน Rutzky

โอ้ไม่เป็นไรหรอก
Michael Green

ไม่มันไม่ทำงาน
Brent Ozar
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.