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.)
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.
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:
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:
read_only_route_complete
est un événement de débogage, utilisez-le avec parcimonie. Prenons l'exemple de l'échantillonnage.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'
)
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:
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 ;-).
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. :)