З DMV можна дізнатися, чи використовується з'єднання ApplicationIntent = ReadOnly?


23

У мене створена група "Завжди в наявності", і я хочу переконатися, що мої користувачі використовують ApplicationIntent = ReadOnly у своїх рядках підключення.

Чи можу я через SQL Server через DMV (або розширені події чи будь-що інше) сказати, чи користувач підключився до ApplicationIntent = ReadOnly у рядку підключення?

Будь ласка, не відповідайте на те, як ПЕРЕДБАЧИТИ з'єднання - це не про це питання. Я не можу просто перестати припиняти з'єднання, оскільки у нас існують додатки, які підключаються без потрібного рядка, і мені потрібно знати, які вони є, щоб я міг працювати з розробниками та користувачами, щоб поступово це фіксувати з часом.

Припустимо, що у користувачів є кілька додатків. Наприклад, Боб з'єднується і з Studio SQL Server Management Studio, і з Excel. Він з'єднується з SSMS, коли йому потрібно робити оновлення, і Excel, коли йому потрібно робити читання. Мені потрібно переконатися, що він використовує ApplicationIntent = ReadOnly, коли він з'єднується з Excel. (Це не точний сценарій, але це досить близько для ілюстрації.)


Я думаю, що лише час читання вирішується під час маршрутизації TDS. Після того, як буде перенаправлена ​​на читабельну вторинну інформацію, інформація більше не потрібна, тому, ймовірно, вона не входить в двигун.
Рем Русану

2
"тільки маршрутизація зчитування спочатку підключається до первинного, а потім шукає найкращий доступний для читання вторинний", здається, що вторинне бачить це як звичайне з'єднання. Якщо буде запущено будь-який XEvent, це було б на первинному. Я не знаю, про що я говорю, але я спекулюю.
Рем Русану

1
@RemusRusanu ви говорите про те, sqlserver.read_only_route_completeяк це спрацьовує лише на первинному.
Кін Шах

@Kin там ви йдете, точно так, як я б це
зашифрував

2
@RemusRusanu Я грав з цим, і я думаю, що це найближче, що ви можете отримати з gotchas - URL-адреса для читання налаштована правильно і немає проблем з підключенням. За обох цих випадків ця подія матиме успіх.
Кін Шах

Відповіді:


10

Збір на sqlserver.read_only_route_completeрозширеному заході, згаданому Kin та Remus, це приємна подія налагодження , але вона не несе з собою велику кількість інформації - просто 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. Подія readonly запускається при успішному вході в систему лише для читання.

Після паузи або зупинки сеансу я можу запитати його і намагатися з'єднати дві події, наприклад:

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_sesions ), або навіть CONNECTIONPROPERTY, яке стосуєтьсяApplicationIntent ключового слова ConnectionString.

Однак, можливо, варто попросити через Microsoft Connect, щоб ця властивість була додана до sys.dm_exec_connectionsDMV, оскільки це, здається, є властивістю з'єднання, яке зберігається десь у пам'яті SQL Server, виходячи з наступної інформації, знайденої на сторінці MSDN для Підтримка SqlClient для високої доступності та відновлення після катастроф (наголос курсивом):

Вказівка ​​наміру програми

Коли ApplicationIntent = ReadOnly , клієнт вимагає прочитати робоче навантаження під час підключення до бази даних, що підтримується AlwaysOn. Сервер буде застосовувати наміри під час підключення та під час оператора бази даних USE, але лише до бази даних, що завжди ввімкнено.

Якщо USEтвердження може бути перевірено, тоді ApplicationIntentпотреби повинні існувати поза початковою спробою з'єднання. Однак я особисто не підтвердив цю поведінку.


PS Я думав, що ми можемо використати факти, які:

  • первинна репліка може бути встановлена ​​для заборони доступу ReadOnly до однієї або декількох баз даних та
  • "намір" буде застосовано, коли USEзаява виконується.

Ідея полягала у створенні нової Бази даних виключно з метою тестування та відстеження цього параметра. Нова БД буде використовуватися в новій групі доступності, яка буде налаштована лише на READ_WRITEз'єднання. Теорія полягала в тому, що всередині тригера входу 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, Context Connection = true;а потім зателефонував SqlConnection.ChangeDatabase()у межах a, try...catchі транзакція все ще була перервана. І ви не можете використовувати Enlist=falseконтекстне з'єднання. І використання звичайного / зовнішнього з'єднання в SQLCLR для виходу за межі транзакції не допоможе, оскільки це було б зовсім новим З'єднанням.

Є дуже, дуже тонка можливість, що HAS_DBACCESS можна використовувати замістьUSE заяви, але я справді не маю великих надій, щоб вона могла включити поточну інформацію про підключення до своїх перевірок. Але я також не можу перевірити це.

Звичайно, якщо є Trace Flag, який може призвести до того, що порушення доступу не може бути перервним у пакетному режимі, тоді план, згаданий вище, повинен працювати ;-).


На жаль, я не можу їх заперечити - інші читабельні репліки можуть бути вниз. Мені ще потрібні запити для читання, щоб працювати над основним - мені просто потрібно знати, коли вони відбуваються.
Брент Озар

@BrentOzar Я оновив свою відповідь, щоб включити новий Крок 3, який перевірить цю умову, і якщо немає доступних Вторинних, то це дозволить З'єднання. Крім того, якщо намір все ще просто "знати, коли відбувається", тоді можна використовувати те саме налаштування, просто змініть ROLLBACKтригер для INSERTвходу на таблицю журналів :-)
Соломон Руцький,

1
це чудова відповідь, але справа не в цьому. Мені не потрібно зупиняти користувачів, мені потрібно стежити, як це відбувається. У нас є наявні додатки, які нам потрібно поступово визначати та виправляти. Якщо я зупинив користувачів від входу, це спричинило б негайний бунт. Якщо ви хочете створити для цього окреме запитання та опублікувати свою відповідь там, це було б чудово - але, будь ласка, зосередьте свою відповідь тут на моєму актуальному питанні. Спасибі.
Брент Озар

@BrentOzar Вибачте, я неправильно зрозумів ваш коментар до Тома як щось трохи сильніше, ніж просто відстеження / ведення журналів. Я видалив частину своєї відповіді, яка стосувалася запобігання доступу.
Соломон Руцький

@BrentOzar Я додав кілька записок під рядком (у розділі PS), який був близьким до рішення, але зірвався в самому кінці. Я розмістив ці замітки на випадок, якщо це викликає ідею у вас (чи когось іншого) придумати пропущений фрагмент або навіть щось зовсім інше, що може вирішити цю загадку.
Соломон Руцький

2

Яким хворим ти хочеш бути? Потік TDS не є таким складним для проксі, ми це зробили для нашої програми SaaS. Шматочок, який ви шукаєте (буквально трохи), міститься у повідомленні login7. Ви можете змусити своїх користувачів підключитися через проксі і ввійти / примусити біт там. Чорт, ти можеш навіть їх увімкнути. :)


Це, безумовно, більше хворий, ніж я хочу бути, але спасибі, ха-ха.
Брент Озар

-1

Чи використовує ваша програма обліковий запис служби чи, можливо, кілька облікових записів послуг? Якщо так, використовуйте Extended Event для контролю вашого трафіку входу, але виключайте облікові записи служби на вашому основному сервері, що постійно працює на сервері. Тепер ви маєте змогу бачити, хто входить на основний сервер, що постійно вмикається, а не використовує лише вторинний рядок з'єднання. Я готуюсь до встановлення Always-On, і це те, що я збираюся робити, якщо ви не скажете мені, що це не вийде.


1
Том - припустимо, що у користувачів є кілька додатків. Наприклад, Боб з'єднується як з Studio SQL Server Management Studio, так і з Excel. Він підключається до SSMS, коли йому потрібно робити оновлення, і Excel, коли йому потрібно робити читання. Мені потрібно переконатися, що він використовує ApplicationIntent = ReadOnly, коли він з'єднується з Excel. (Це не точний сценарій, але це досить близько для ілюстрації.)
Брент Озар,

У мене також є люди, які підключаються до мого виробничого сервера з Excel з дуже обмеженим доступом. Вони пов'язують свої права. Я сподіваюся, що зможу їх побачити. Незабаром ми підведемо наш Завжди.
ArmorDba

-1

На жаль, у мене немає середовища, щоб перевірити наступне, і, безсумнівно, є кілька моментів, коли це може вийти з ладу, але я викину його туди, на що воно варте.

Процедура, що зберігається CLR, має доступ до поточного з'єднання через new SqlConnection("context connection=true")конструкцію (взята звідси ). Тип SqlConnection розкриває властивість ConnectionString . Оскільки ApplicationIntent знаходиться в початковій рядку з'єднання, я припускаю, що він буде доступний у цій властивості та може бути розібраний. Звичайно, в цьому ланцюжку багато роздачі, тому так багато можливостей для того, щоб усі пройшли грушоподібну форму.

Це запускається з тригера входу, і необхідні значення зберігатимуться за потребою.


1
Це не працює. Код SQLCLR не має доступу до поточного з’єднання, він має доступ до поточної сесії через контекстне з'єднання. Об'єкт SqlConnection в .NET-коді не підключається до фактичного з'єднання, здійсненого з оригінального програмного забезпечення клієнта в SQL Server. Це дві окремі речі.
Соломон Руцький

Ну добре, майте на увазі тоді.
Майкл Грін

Ні, це не працює.
Брент Озар
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.