DMV에서 연결에 ApplicationIntent = ReadOnly가 사용되었는지 알 수 있습니까?


23

Always On 가용성 그룹을 설정했으며 사용자가 연결 문자열에서 ApplicationIntent = ReadOnly를 사용하고 있는지 확인하고 싶습니다.

DMV (또는 확장 이벤트 등)를 통해 SQL Server에서 사용자가 연결 문자열에서 ApplicationIntent = ReadOnly에 연결되었는지 알 수 있습니까?

연결을 예방하는 방법에 대해서는 대답하지 마십시오.이 질문이 아닙니다. 올바른 문자열없이 연결하는 기존 응용 프로그램이 있기 때문에 단순히 연결을 중지 할 수 없으며 응용 프로그램이 무엇인지 알아야하므로 개발자 및 사용자와 함께 시간이 지남에 따라 점차 수정 될 수 있습니다.

사용자에게 여러 응용 프로그램이 있다고 가정하십시오. 예를 들어 Bob은 SQL Server Management Studio 및 Excel과 연결합니다. 업데이트가 필요할 때는 SSMS에 연결하고, 읽을 때는 Excel에 연결합니다. 그가 Excel과 연결할 때 ApplicationIntent = ReadOnly를 사용하고 있는지 확인해야합니다. (정확한 시나리오는 아니지만 설명하기에 충분합니다.)


나는 생각 읽기 전용 TDS 라우팅시에 결정됩니다. 일단 읽기 가능한 2 차로 라우팅되면 정보가 더 이상 필요하지 않으므로 엔진에 정보를 제공하지 않습니다.
Remus Rusanu

2
"읽기 전용 라우팅은 먼저 기본에 연결 한 다음 사용 가능한 최상의 읽기 가능한 보조를 찾습니다." 보조는 일반 연결로 간주합니다. XEvent가 트리거되면 기본에있을 것입니다. 내가 무슨 말을하는지 모르겠지만 추측하고 있습니다.
Remus Rusanu 2016 년

1
@RemusRusanu는 sqlserver.read_only_route_complete기본에서만 트리거되므로 이야기하고 있습니까 ?
Kin Shah

@Kin 거기 당신이 가고, 내가 정확히 코드를 작성했을 것입니다;)
Remus Rusanu

2
@RemusRusanu 나는 그것을 가지고 놀고 있었고, 당신이 getchas를 얻을 수있는 가장 가까운 것 같아요-읽기 전용 URL이 올바르게 구성되어 있으며 연결 문제가 없습니다. 두 경우 모두 해당 이벤트가 성공합니다.
Kin Shah

답변:


10

sqlserver.read_only_route_completeKin과 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은 읽을 수있는 2 차이고 sqlserver-1은 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

아니요, sys.dm_exec_connections 또는 sys.dm_exec_sessions 에 DMV 노출 연결 속성이 있거나 ConnectionString 키워드 와 관련된 CONNECTIONPROPERTY 가있는 것으로 보이지 않습니다 ApplicationIntent.

그러나 MSDN 페이지에있는 다음 정보를 기반으로 SQL Server의 메모리에 저장되어있는 연결의 속성 인 것처럼 이 속성을 sys.dm_exec_connectionsDMV에 추가하도록 Microsoft Connect를 통해 요청하는 것이 좋습니다. 고 가용성, 재해 복구를위한 SqlClient 지원 (이탈리아어 강조) :

응용 프로그램 의도 지정

ApplicationIntent = 읽기 전용은 상시 데이터베이스를 활성화에 연결할 때 클라이언트는 읽기 작업을 요청합니다. 서버는 연결시 및 USE 데이터베이스 명령문 중에 의도를 강제 하지만 Always On 사용 데이터베이스에만 적용합니다.

USE명령문을 확인할 수 있으면 ApplicationIntent초기 연결 시도를 넘어서서 요구 사항이 존재해야합니다. 그러나 개인적으로이 동작을 확인하지 않았습니다.


추신 : 나는 우리가 다음과 같은 사실을 활용할 수 있다고 생각했습니다.

  • 기본 복제본은 하나 이상의 데이터베이스에 대한 읽기 전용 액세스를 허용하지 않도록 설정할 수 있습니다.
  • "의도"는 USE명령문이 실행될 때 시행됩니다 .

아이디어는이 설정을 테스트하고 추적 할 목적으로 만 새 데이터베이스를 작성하는 것이 었습니다. 새 DB는 READ_WRITE연결 만 허용하도록 설정된 새 가용성 그룹에서 사용됩니다 . 이론적으로는 Logon Trigger 내부 , 블록 내에 기본적으로 아무것도없는 구조 EXEC(N'USE [ReadWriteOnly]; INSERT INTO LogTable...;');내에서 ReadWrite 연결 (새로운 DB에 자체 기록)에 대해 오류가 발생하지 않거나 ReadOnly 연결에 오류가 발생하지만 오류가 발생하여 무시 된 이후에는 아무 일도 일어나지 않습니다 (그리고 그 진술에 결코 도달하지 못할 것입니다). 두 경우 모두 실제 로그온 이벤트는 예방 / 거부 되지 않습니다 . 로그온 트리거 코드는 효과적으로 다음과 같습니다.TRY...CATCHCATCHUSEINSERT

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;

불행히도 Transaction 내부 에서 USE명령문 을 발행하는 효과를 테스트 할 때 액세스 위반이 명령문 레벨 중단이 아니라 배치 레벨 중단이라는 것을 알았습니다. 그리고 설정 은 아무것도 바뀌지 않았습니다. 심지어 사용하기 위해 간단한 SQLCLR 저장 프로 시저를 만든 다음 a 내에서 호출 했는데 트랜잭션이 여전히 중단되었습니다. 그리고 컨텍스트 연결 에서는 사용할 수 없습니다 . 그리고 SQLCLR에서 규칙 / 외부 연결을 사용하여 트랜잭션 외부로 나가는 것은 완전히 새로운 연결이므로 도움이되지 않습니다.EXEC()TRY...CATCHXACT_ABORT OFFContext Connection = true;SqlConnection.ChangeDatabase()try...catchEnlist=false

명령문 대신 HAS_DBACCESS를 사용할 수있는 가능성은 매우 적지USE 만 현재 연결 정보를 검사에 통합 할 수있을 것이라는 희망은 없습니다. 그러나 나는 그것을 테스트 할 방법이 없다.

물론, 액세스 위반이 일괄 중단되지 않도록하는 추적 플래그가있는 경우 위에서 언급 한 계획이 작동해야합니다. ;-).


불행히도, 나는 그들을 거부 할 수 없다-다른 읽을 수있는 복제본은 다운 될 수있다. 여전히 기본 쿼리에서 작동하는 읽기 쿼리가 필요합니다. 언제 발생하는지 알아야합니다.
브렌트 오자

@BrentOzar 나는 그 상태를 확인하는 새로운 3 단계를 포함하도록 내 대답을 업데이트했으며 사용 가능한 2 차가 없으면 연결을 허용합니다. 의도는 여전히에있는 경우 또한, 다음 같은 설정을 사용할 수 있습니다, 단지 변경 "thy're 일어나는 때 알고있다" ROLLBACK에 로그인 트리거에 INSERT:-) 로그 테이블에
솔로몬 Rutzky

1
이것은 훌륭한 답변이지만이 질문에는 해당되지 않습니다. 사용자를 막을 필요가 없습니다. 언제 발생하는지 모니터링해야합니다. 점진적으로 파악하고 수정해야하는 기존 앱이 있습니다. 사용자 로그인을 중지하면 즉시 반란이 발생합니다. 이에 대한 별도의 질문을 작성하고 거기에 답변을 게시하려면 훌륭합니다. 그러나 여기서 실제 답변에 초점을 맞추십시오. 감사.
브렌트 오자

@BrentOzar 죄송합니다. 추적 / 로깅보다 조금 더 강한 의미로 Tom에 대한 귀하의 의견을 오해했습니다. 액세스 방지를 다루는 답변의 일부를 제거했습니다.
Solomon Rutzky 2016 년

@BrentOzar 나는 해결책에 가깝지 만 줄 아래에 (PS 섹션의) 메모를 추가했지만 결국 막혔습니다. 나는이 퍼즐을 해결할 수있는 누락 된 조각 또는 완전히 다른 무언가를 생각해내는 것이 당신이나 다른 사람에게 아이디어를 촉발시키는 경우를 대비하여 메모를 게시했습니다.
Solomon Rutzky 2016 년

2

얼마나 아픈가? TDS 스트림은 프록시하기가 어렵지 않으며 SaaS 앱을 위해 수행했습니다. 찾고있는 비트 (문자 그대로 비트)는 login7 메시지에 있습니다. 사용자가 프록시를 통해 연결하고 비트를 기록 / 강화하도록 할 수 있습니다. 지옥, 당신은 그들을 위해 그것을 켤 수 있습니다. :)


그것은 내가 원하는 것보다 확실히 더 아파요.하지만 고마워요.
브렌트 오자

-1

응용 프로그램에서 서비스 계정이나 여러 서비스 계정을 사용합니까? 그렇다면 확장 이벤트를 사용하여 로그인 트래픽을 모니터링하되 기본 상시 가동 서버에서 서비스 계정은 제외하십시오. 이제 기본 Always-On 서버에 로그인하고 읽기 전용 보조 연결 문자열을 사용하지 않는 사람을 볼 수 있습니다. Always-On을 설치할 준비가되었습니다. 이것이 작동하지 않는다고 말하지 않으면이 작업을 수행하게됩니다.


1
Tom-사용자가 여러 응용 프로그램을 가지고 있다고 가정합니다. 예를 들어 Bob은 SQL Server Management Studio 및 Excel과 연결합니다. 업데이트가 필요할 때는 SSMS에 연결하고, 읽을 때는 Excel에 연결합니다. 그가 Excel과 연결할 때 ApplicationIntent = ReadOnly를 사용하고 있는지 확인해야합니다. (정확한 시나리오는 아니지만 설명하기에 충분합니다.)
Brent Ozar

또한 매우 제한된 액세스 권한을 가진 Excel로 프로덕션 서버에 연결하는 사람들이 있습니다. 그들은 그들의 권리와 연결됩니다. 나는 그들을 볼 수 있기를 바랍니다. 우리는 Always On을 곧 시작할 것입니다.
ArmorDba

-1

불행히도 나는 다음을 테스트 할 환경이 없으며 의심 할 여지없이 몇 가지 포인트가 있지만 가치가있는 것을 버릴 것입니다.

CLR 저장 프로시 저는 new SqlConnection("context connection=true")구문 ( 여기 에서 가져온)을 통해 현재 연결에 액세스 할 수 있습니다 . SqlConnection 형식은 ConnectionString 속성을 노출 합니다. ApplicationIntent가 초기 연결 문자열에 있기 때문에이 속성에서 사용할 수 있으며 파싱 될 수 있다고 가정합니다. 물론 그 체인에는 많은 핸드 오프가 있습니다. 그래서 모든 것이 배 모양으로 갈 수있는 많은 기회가 있습니다.

이것은 로그온 트리거 에서 실행되며 필요한 값은 필요에 따라 유지됩니다.


1
이 작동하지 않습니다. SQLCLR 코드는 현재 연결에 액세스 할 수 없으며 컨텍스트 연결을 통해 현재 세션에 액세스 할 수 있습니다. .NET 코드의 SqlConnection 개체가 원래 클라이언트 소프트웨어에서 SQL Server로 이루어진 실제 연결을 사용하지 않습니다. 그것들은 별개의 두 가지입니다.
Solomon Rutzky 2016 년

오, 그럼 신경 쓰지 마
마이클 그린

아니요, 작동하지 않습니다.
브렌트 오자
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.