Können Sie anhand der DMVs feststellen, ob für eine Verbindung ApplicationIntent = ReadOnly verwendet wurde?

23

Ich habe eine Always On Availability-Gruppe eingerichtet und möchte sicherstellen, dass meine Benutzer ApplicationIntent = ReadOnly in ihren Verbindungszeichenfolgen verwenden.

Kann ich vom SQL Server über DMVs (oder Extended Events oder was auch immer) feststellen, ob ein mit ApplicationIntent = ReadOnly verbundener Benutzer in seiner Verbindungszeichenfolge enthalten ist?

Bitte antworten Sie nicht, wie Sie Verbindungen VERMEIDEN - darum geht es in dieser Frage nicht. Ich kann nicht einfach Verbindungen beenden, da es bereits Anwendungen gibt, die keine Verbindung mit der richtigen Zeichenfolge herstellen, und ich muss wissen, um welche es sich handelt, damit ich mit den Entwicklern und Benutzern zusammenarbeiten kann, um sie im Laufe der Zeit schrittweise zu reparieren.

Angenommen, Benutzer haben mehrere Anwendungen. Bob stellt beispielsweise eine Verbindung zu SQL Server Management Studio und Excel her. Er stellt eine Verbindung zu SSMS her, wenn Aktualisierungen erforderlich sind, und Excel, wenn Lesevorgänge erforderlich sind. Ich muss sicherstellen, dass er ApplicationIntent = ReadOnly verwendet, wenn er eine Verbindung mit Excel herstellt. (Das ist nicht das genaue Szenario, aber es ist nah genug, um es zu veranschaulichen.)

Brent Ozar
quelle
Ich denke, dass schreibgeschützt zum Zeitpunkt des TDS-Routings entschieden wird. Sobald die Informationen an eine lesbare Sekundärseite weitergeleitet wurden, werden sie nicht mehr benötigt, sodass sie wahrscheinlich nicht in die Engine gelangen.
Remus Rusanu
2
"Nur-Lese-Routing verbindet sich zuerst mit dem primären und sucht dann nach dem besten verfügbaren lesbaren sekundären", so scheint es, als würde der sekundäre Router dies als eine gewöhnliche Verbindung ansehen. Wenn ein XEvent ausgelöst wird, befindet es sich auf der primären Ebene. Ich weiß nicht wovon ich spreche, aber ich spekuliere.
Remus Rusanu
1
@ RemusRusanu redest du, sqlserver.read_only_route_completeda es nur auf primären ausgelöst wird.
Kin Shah
@Kin los gehts, genau so wie ich es codieren würde;)
Remus Rusanu
2
@RemusRusanu Ich habe damit gespielt und ich denke, es ist das Beste, was Sie mit Fallstricken bekommen können - die schreibgeschützte URL ist richtig konfiguriert und es gibt keine Verbindungsprobleme. In beiden Fällen ist das Ereignis erfolgreich.
Kin Shah

Antworten:

10

Das sqlserver.read_only_route_completevon Kin und Remus erwähnte Extended Event ist ein nettes Debug- Ereignis, das jedoch nicht viele Informationen enthält - standardmäßig nur route_port(z. B. 1433) und route_server_name(z. B. sqlserver-0.contoso.com) . Dies würde auch nur dazu beitragen, festzustellen, wann eine Nur-Lese-Verbindung erfolgreich war. Es gibt ein read_only_route_failEreignis, aber ich konnte es nicht auslösen. Wenn ein Problem mit der Routing-URL aufgetreten ist, schien es nicht ausgelöst zu werden, als die sekundäre Instanz nicht verfügbar war / heruntergefahren wurde, soweit ich das beurteilen konnte.

Ich hatte jedoch einige Erfolge, als ich das sqlserver.loginEreignis- und Kausalitäts-Tracking aktivierte und einige Aktionen (wie sqlserver.username), um es nützlich zu machen.

Schritte zum Reproduzieren

Erstellen Sie eine erweiterte Ereignissitzung, um relevante Ereignisse sowie nützliche Aktionen und die Kausalität zu verfolgen:

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
)

Führen Sie die XE-Sitzung aus (betrachten Sie das Sampling als Debug-Ereignis) und sammeln Sie einige Anmeldungen:

sqlcmd-Verbindungen

Beachten Sie, dass sqlserver-0 meine lesbare sekundäre und sqlserver-1 die primäre ist. Hier verwende ich den -KSchalter von sqlcmd, um schreibgeschützte Anmeldungen mit Anwendungsabsicht und einige SQL-Anmeldungen zu simulieren. Das schreibgeschützte Ereignis wird bei einer erfolgreichen Nur-Lese-Anmeldung ausgelöst.

Wenn ich die Sitzung pausiere oder stoppe, kann ich sie abfragen und versuchen, die beiden Ereignisse zu verknüpfen, z.

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

Die Abfrage sollte die Anmeldungen mit und ohne Nur-Lese-Absicht der Anwendung anzeigen:

Abfrageergebnisse

  • read_only_route_completeist ein Debug-Ereignis, verwenden Sie es daher sparsam. Betrachten Sie zum Beispiel Stichproben.
  • Die beiden Events bieten zusammen mit der Track-Kausalität das Potenzial, Ihre Anforderungen zu erfüllen - weitere Tests auf diesem einfachen Rig sind erforderlich
  • Ich habe bemerkt, wenn der Datenbankname nicht in der Verbindung angegeben wurde, die Dinge schienen nicht zu funktionieren
  • Ich habe versucht, das pair_matchingZiel zum Arbeiten zu bringen , aber mir ging die Zeit davon. Hier gibt es ein gewisses Entwicklungspotential, so etwas wie:

    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
quelle
5

Nein, anscheinend gibt es keine DMV-exponierte Verbindungseigenschaft (entweder in sys.dm_exec_connections oder sys.dm_exec_sessions ) oder sogar CONNECTIONPROPERTY , die sich auf das ApplicationIntentConnectionString-Schlüsselwort bezieht .

Es kann sich jedoch lohnen, über Microsoft Connect anzufordern, dass diese Eigenschaft zur sys.dm_exec_connectionsDMV hinzugefügt wird, da sie anscheinend eine Eigenschaft der Verbindung ist, die auf der Grundlage der folgenden Informationen auf der MSDN-Seite für irgendwo im Speicher von SQL Server gespeichert ist SqlClient-Unterstützung für Hochverfügbarkeit und Notfallwiederherstellung (Hervorhebung kursiv gedruckt ):

Angeben der Anwendungsabsicht

Bei ApplicationIntent = ReadOnly fordert der Client eine Lese-Workload an, wenn eine Verbindung zu einer AlwaysOn-fähigen Datenbank hergestellt wird. Der Server erzwingt die Absicht zur Verbindungszeit und während einer USE-Datenbankanweisung, jedoch nur für eine Always On-fähige Datenbank.

Wenn eine USEAnweisung überprüft werden kann, ApplicationIntentmuss sie nach dem ersten Verbindungsversuch vorhanden sein. Ich habe dieses Verhalten jedoch nicht persönlich überprüft.


PS Ich hatte gedacht, wir könnten die Fakten nutzen, die:

  • Ein primäres Replikat kann so eingestellt werden, dass der schreibgeschützte Zugriff auf eine oder mehrere Datenbanken nicht zulässig ist
  • Die "Absicht" wird erzwungen, wenn eine USEAnweisung ausgeführt wird.

Die Idee war, eine neue Datenbank zu erstellen, um diese Einstellung zu testen und zu verfolgen. Die neue Datenbank würde in einer neuen Verfügbarkeitsgruppe verwendet, die nur READ_WRITEVerbindungen zulässt . Die Theorie war, dass innerhalb eines Logon-Triggers, eines EXEC(N'USE [ReadWriteOnly]; INSERT INTO LogTable...;');innerhalb eines TRY...CATCHKonstrukts, mit im Wesentlichen nichts im CATCHBlock, entweder kein Fehler für ReadWrite-Verbindungen (die sich in der neuen DB anmelden würden) oder der Fehler für ReadOnly-Verbindungen erzeugt würde USE, aber dann würde nichts passieren, da der Fehler abgefangen und ignoriert wird (und die INSERTAussage niemals erreicht würde). In beiden Fällen wird das tatsächliche Anmeldeereignis nicht verhindert / verweigert. Der Anmelde-Trigger-Code wäre effektiv:

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;

Leider stellte ich beim Testen der Auswirkung der Ausgabe einer USEAnweisung in EXEC()einem TRY...CATCHInneren einer Transaktion fest, dass die Zugriffsverletzung ein Abbruch auf Stapelebene und kein Abbruch auf Anweisungsebene war. Und die Einstellung XACT_ABORT OFFhat nichts geändert. Ich habe sogar eine einfache gespeicherte SQLCLR-Prozedur erstellt, die verwendet Context Connection = true;und dann SqlConnection.ChangeDatabase()innerhalb von a aufgerufen wurde, try...catchund die Transaktion wurde dennoch abgebrochen. Und Sie können nicht Enlist=falsefür die Kontextverbindung verwenden. Die Verwendung einer regulären / externen Verbindung in SQLCLR zum Verlassen der Transaktion würde nicht helfen, da es sich um eine ganz neue Verbindung handeln würde.

Es gibt eine sehr, sehr geringe Möglichkeit, dass HAS_DBACCESS anstelle der USEAnweisung verwendet werden könnte, aber ich habe wirklich keine großen Hoffnungen, dass es in der Lage ist, aktuelle Verbindungsinformationen in seine Prüfungen einzubeziehen. Aber ich kann es auch nicht testen.

Natürlich sollte der oben erwähnte Plan funktionieren, wenn es ein Ablaufverfolgungsflag gibt, das dazu führen kann, dass die Zugriffsverletzung nicht stapelweise abgebrochen wird ;-).

Solomon Rutzky
quelle
Leider kann ich sie nicht leugnen - die anderen lesbaren Repliken könnten ausgefallen sein. Ich brauche die Leseabfragen immer noch, um auf der Primärseite zu arbeiten - ich muss nur wissen, wann sie stattfinden.
Brent Ozar
@BrentOzar Ich habe meine Antwort so aktualisiert, dass sie einen neuen Schritt 3 enthält, der diese Bedingung überprüft. Wenn keine sekundären Adressen verfügbar sind, wird die Verbindung zugelassen. Auch wenn die Absicht besteht, nur "zu wissen, wann dein Ereignis passiert", kann das gleiche Setup verwendet werden, ändern Sie einfach den ROLLBACKim Login-Trigger INSERTin eine Protokolltabelle :-)
Solomon Rutzky
1
Das ist eine gute Antwort, aber nicht für diese Frage. Ich muss die Benutzer nicht anhalten, sondern muss überwachen, wann dies geschieht. Es gibt bereits Apps, die wir schrittweise lokalisieren und korrigieren müssen. Wenn ich Benutzer daran hindern würde, sich einzuloggen, würde dies eine sofortige Revolte auslösen. Wenn Sie eine separate Frage dazu erstellen und Ihre Antwort dort posten möchten, wäre das großartig - aber konzentrieren Sie Ihre Antwort hier auf meine eigentliche Frage. Vielen Dank.
Brent Ozar
@BrentOzar Entschuldigung, ich habe Ihren Kommentar zu Tom falsch verstanden, da er etwas Stärkeres bedeutet als nur Tracking / Logging. Ich habe den Teil meiner Antwort entfernt, der sich mit der Verhinderung des Zugriffs befasste.
Solomon Rutzky
@BrentOzar Ich habe einige Anmerkungen unter der Zeile (im PS-Bereich) hinzugefügt, die der Lösung nahe waren, aber am Ende vereitelt wurden. Ich habe diese Notizen gepostet, falls Sie (oder jemand anderes) auf die Idee kommen, das fehlende Teil oder sogar etwas ganz anderes zu finden, das dieses Rätsel lösen könnte.
Solomon Rutzky
2

Wie krank möchtest du sein? Der TDS-Stream ist nicht so schwer zu proxen, wir haben es für unsere SaaS-App gemacht. Das gesuchte Bit (im wahrsten Sinne des Wortes ein bisschen) ist in der login7-Nachricht enthalten. Sie könnten Ihre Benutzer über einen Proxy verbinden und das Bit dort protokollieren / erzwingen lassen. Zum Teufel, du könntest es sogar für sie einschalten. :)

Walden Leverich
quelle
Das ist definitiv kranker, als ich sein möchte, aber danke, hahaha.
Brent Ozar
-1

Verwendet Ihre Anwendung ein Dienstkonto oder mehrere Dienstkonten? Verwenden Sie in diesem Fall Extended Event, um Ihren Anmeldeverkehr zu überwachen, schließen Sie jedoch Ihre Dienstkonten auf Ihrem primären Always-On-Server aus. Sie sollten jetzt in der Lage sein zu sehen, wer sich auf dem primären Always-On-Server anmeldet und nicht die schreibgeschützte sekundäre Verbindungszeichenfolge verwendet. Ich bereite mich auf die Installation von Always-On vor und werde dies tun, sofern Sie mir nicht mitteilen, dass dies nicht funktioniert.

ArmorDba
quelle
1
Tom - Angenommen, Benutzer haben mehrere Anwendungen. Bob stellt beispielsweise eine Verbindung zu SQL Server Management Studio und Excel her. Er stellt eine Verbindung zu SSMS her, wenn Aktualisierungen erforderlich sind, und Excel, wenn Lesevorgänge erforderlich sind. Ich muss sicherstellen, dass er ApplicationIntent = ReadOnly verwendet, wenn er eine Verbindung mit Excel herstellt. (Das ist nicht das genaue Szenario, aber es ist nah genug, um es zu veranschaulichen.)
Brent Ozar
Ich habe auch Leute, die mit Excel eine Verbindung zu meinem Produktionsserver herstellen und nur sehr eingeschränkten Zugriff haben. Sie verbinden sich mit ihren Rechten. Ich hoffe, dass ich sie sehen kann. Wir werden uns in Kürze mit Always On befassen.
ArmorDba
-1

Leider habe ich nicht die Umgebung, um Folgendes zu testen, und es gibt zweifellos mehrere Punkte, an denen es fehlschlagen kann, aber ich werde es für das, was es wert ist, da rausschmeißen.

Eine gespeicherte CLR-Prozedur hat über das new SqlConnection("context connection=true")Konstrukt Zugriff auf die aktuelle Verbindung (von hier aus ). Der SqlConnection-Typ macht eine ConnectionString- Eigenschaft verfügbar . Da ApplicationIntent in der anfänglichen Verbindungszeichenfolge enthalten ist, wird diese vermutlich in dieser Eigenschaft verfügbar sein und kann analysiert werden. Natürlich gibt es eine Menge Übergaben in dieser Kette, so viele Möglichkeiten, dass alles birnenförmig wird.

Dies würde von einem Anmeldetrigger ausgeführt, und die erforderlichen Werte würden nach Bedarf beibehalten.

Michael Green
quelle
1
Das würde nicht funktionieren. Der SQLCLR-Code hat keinen Zugriff auf die aktuelle Verbindung, sondern über die Kontextverbindung Zugriff auf die aktuelle Sitzung. Das SqlConnection-Objekt im .NET-Code greift nicht auf die tatsächliche Verbindung zu SQL Server zu, die von der ursprünglichen Client-Software hergestellt wurde. Das sind zwei verschiedene Dinge.
Solomon Rutzky
Na ja, egal dann.
Michael Green
Nein, das geht nicht.
Brent Ozar