web-dev-qa-db-fra.com

À partir des DMV, pouvez-vous dire si une connexion a utilisé ApplicationIntent = ReadOnly?

J'ai un groupe de disponibilité Always On configuré et je veux m'assurer que mes utilisateurs utilisent ApplicationIntent = ReadOnly dans leurs chaînes de connexion.

À partir de SQL Server via DMV (ou événements étendus ou autre), puis-je savoir si un utilisateur connecté avec ApplicationIntent = ReadOnly dans sa chaîne de connexion?

Veuillez ne pas répondre à la façon de PRÉVENIR les connexions - ce n'est pas le sujet de cette question. Je ne peux pas simplement arrêter les connexions, car nous avons des applications existantes qui se connectent sans la bonne chaîne, et j'ai besoin de savoir lesquelles elles sont afin que je puisse travailler avec les développeurs et les utilisateurs pour les réparer progressivement au fil du temps.

Supposons que les utilisateurs disposent de plusieurs applications. Par exemple, Bob se connecte à SQL Server Management Studio et à Excel. Il se connecte à SSMS lorsqu'il doit effectuer des mises à jour et à Excel lorsqu'il doit effectuer des lectures. Je dois m'assurer qu'il utilise ApplicationIntent = ReadOnly lorsqu'il se connecte à Excel. (Ce n'est pas le scénario exact, mais il est assez proche pour illustrer.)

24
Brent Ozar

Reprenant le sqlserver.read_only_route_complete Événement étendu mentionné par Kin et Remus, c'est un événement Nice Debug , mais il ne contient pas beaucoup d'informations avec lui - juste route_port (par exemple 1433) et route_server_name (par exemple sqlserver-0.contoso.com) par défaut. Cela aiderait également à déterminer quand une connexion intentionnelle en lecture seule a réussi. Il y a un read_only_route_fail événement mais je n'ai pas pu le déclencher, peut-être qu'en cas de problème avec l'URL de routage, il ne semblait pas se déclencher lorsque l'instance secondaire n'était pas disponible/arrêtée pour autant que je sache.

J'ai cependant eu un certain succès en joignant cela au sqlserver.login suivi des événements et du lien de causalité activé, ainsi que certaines actions (comme sqlserver.username) pour le rendre utile.

Étapes à reproduire

Créez une session d'événements étendus pour suivre les événements pertinents, ainsi que des actions utiles et suivre le lien de causalité:

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
)

Exécutez la session XE (pensez à l'échantillonnage car il s'agit d'un événement de débogage) et collectez quelques connexions:

sqlcmd connections

Notez ici que sqlserver-0 est mon secondaire lisible et sqlserver-1 le principal. Ici, j'utilise le -K commutateur de sqlcmd pour simuler les connexions d'intention d'application en lecture seule et certaines connexions SQL. L'événement en lecture seule se déclenche sur une connexion d'intention en lecture seule réussie.

En interrompant ou en arrêtant la session, je peux l'interroger et tenter de lier les deux événements, par exemple:

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

La requête doit afficher les connexions avec et sans intention de lecture seule de l'application:

Query Results

  • read_only_route_complete est un événement de débogage, utilisez-le avec parcimonie. Prenons l'exemple de l'échantillonnage.
  • les deux événements ainsi que le lien de causalité offrent la possibilité de répondre à vos besoins - des tests supplémentaires sont nécessaires sur cette plate-forme simple
  • J'ai remarqué que si le nom de la base de données n'était pas spécifié dans la connexion, les choses ne semblaient pas fonctionner
  • J'ai essayé d'obtenir pair_matching objectif de travailler mais manque de temps. Il y a un potentiel de développement ici, quelque chose comme:

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

Non, il ne semble pas y avoir de propriété de connexion exposée DMV (soit dans sys.dm_exec_connections ou sys.dm_exec_sessions ) ou même CONNECTIONPROPERTY qui se rapporte au mot clé ApplicationIntent ConnectionString.

Cependant, il peut être utile de demander, via Microsoft Connect, que cette propriété soit ajoutée au DMV sys.dm_exec_connections Car elle apparaît pour être une propriété de la connexion qui est stockée quelque part dans La mémoire de SQL Server, basée sur les informations suivantes trouvées sur la page MSDN pour Prise en charge de SqlClient pour la haute disponibilité, récupération après sinistre (accentuation en italique):

Spécification de l'intention de l'application

Lorsque ApplicationIntent = ReadOnly , le client demande une charge de travail de lecture lors de la connexion à une base de données activée AlwaysOn. Le serveur appliquera l'intention au moment de la connexion et pendant une instruction USE database mais uniquement sur une base de données Always On.

Si une instruction USE peut être vérifiée, alors ApplicationIntent doit exister au-delà de la tentative de connexion initiale. Cependant, je n'ai pas personnellement vérifié ce comportement.


P.S. J'avais pensé que nous pourrions utiliser les faits qui:

  • un réplica principal peut être défini pour interdire l'accès en lecture seule à une ou plusieurs bases de données, et
  • l '"intention" sera appliquée lors de l'exécution d'une instruction USE.

L'idée était de créer une nouvelle base de données uniquement dans le but de tester et de suivre ce paramètre. La nouvelle base de données serait utilisée dans un nouveau groupe de disponibilité qui serait défini pour n'autoriser que les connexions READ_WRITE. La théorie était qu'à l'intérieur d'un déclencheur d'ouverture de session, une EXEC(N'USE [ReadWriteOnly]; INSERT INTO LogTable...;'); dans une construction TRY...CATCH, Avec essentiellement rien dans le bloc CATCH, ne produirait aucune erreur pour les connexions ReadWrite ( qui se connecterait dans la nouvelle base de données), ou le USE ferait une erreur sur les connexions en lecture seule, mais alors rien ne se passerait puisque l'erreur est interceptée et ignorée (et l'instruction INSERT ne serait jamais atteint). Dans les deux cas, l'événement de connexion réel serait pas empêché/refusé. Le code d'ouverture de session serait effectivement:

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;

Malheureusement, lors du test de l'effet de l'émission d'une instruction USE dans une EXEC() dans un TRY...CATCH À l'intérieur d'une transaction, j'ai constaté que la violation d'accès était une interruption au niveau du lot , pas un abandon au niveau de l'instruction. Et le réglage de XACT_ABORT OFF N'a rien changé. J'ai même créé une procédure stockée SQLCLR simple pour utiliser Context Connection = true;, Puis j'ai appelé SqlConnection.ChangeDatabase() dans un try...catch Et la transaction était toujours abandonnée. Et vous ne pouvez pas utiliser Enlist=false Sur la connexion contextuelle. Et l'utilisation d'une connexion régulière/externe dans SQLCLR pour sortir de la transaction n'aiderait pas car ce serait une toute nouvelle connexion.

Il y a une possibilité très, très mince que HAS_DBACCESS puisse être utilisé à la place de l'instruction USE, mais je n'ai vraiment pas grand espoir qu'il puisse incorporer les informations de connexion actuelles dans ses chèques. Mais je n'ai aucun moyen de le tester non plus.

Bien sûr, s'il existe un indicateur de trace qui peut empêcher la violation d'accès de se terminer par lots, le plan mentionné ci-dessus devrait fonctionner ;-).

5
Solomon Rutzky

Comment voulez-vous être malade? Le flux TDS n'est pas si difficile à proxy, nous l'avons fait pour notre application SaaS. Le bit que vous recherchez (littéralement un peu) est dans le message login7. Vous pouvez avoir votre les utilisateurs se connectent via un proxy et se connectent/appliquent le bit là-bas. Enfer, vous pouvez même l'activer pour eux. :)

2
Walden Leverich