Từ DMV, bạn có thể biết liệu một kết nối đã sử dụng ApplicationIntent = ReadOnly không?


23

Tôi đã thiết lập Nhóm Luôn sẵn sàng và tôi muốn đảm bảo rằng người dùng của tôi đang sử dụng ApplicationIntent = ReadOnly trong chuỗi kết nối của họ.

Từ Máy chủ SQL qua DMV (hoặc Sự kiện mở rộng hoặc bất cứ điều gì), tôi có thể biết liệu người dùng có kết nối với ApplicationIntent = ReadOnly trong chuỗi kết nối của họ không?

Vui lòng không trả lời với cách TRƯỚC kết nối - đó không phải là câu hỏi này. Tôi không thể đơn giản dừng các kết nối, bởi vì chúng tôi có các ứng dụng hiện có đang kết nối mà không có chuỗi phù hợp và tôi cần biết chúng là những ứng dụng nào để tôi có thể làm việc với các nhà phát triển và người dùng để khắc phục dần dần theo thời gian.

Giả sử rằng người dùng có nhiều ứng dụng. Ví dụ: Bob kết nối với SQL Server Management Studio và với Excel. Anh ta kết nối với SSMS khi anh ta cần cập nhật và Excel khi anh ta cần đọc. Tôi cần đảm bảo rằng anh ấy đang sử dụng ApplicationIntent = ReadOnly khi anh ấy kết nối với Excel. (Đó không phải là kịch bản chính xác, nhưng nó đủ gần để minh họa.)


Tôi nghĩ rằng chỉ đọc được quyết định tại thời điểm định tuyến TDS. Một khi được định tuyến đến một thứ cấp có thể đọc được, thông tin không còn cần thiết nữa nên có lẽ nó không được đưa vào công cụ.
Remus Rusanu

2
"Định tuyến chỉ đọc đầu tiên kết nối với chính và sau đó tìm kiếm thứ cấp có thể đọc tốt nhất", có vẻ như thứ cấp sẽ xem nó như một kết nối thông thường. Nếu có bất kỳ XEvent nào được kích hoạt, nó sẽ là chính. Tôi không biết tôi đang nói về cái gì, nhưng tôi đang suy đoán.
Remus Rusanu

1
@RemusRusanu bạn đang nói về sqlserver.read_only_route_completenó chỉ được kích hoạt trên chính.
Kin Shah

@Kin bạn đi, chính xác như tôi đã có mã đó;)
Remus Rusanu

2
@RemusRusanu Tôi đã chơi với nó và tôi đoán nó gần nhất mà bạn có thể nhận được với gotchas - URL chỉ đọc được cấu hình đúng và không có vấn đề kết nối. Trong cả hai trường hợp đó, sự kiện đó sẽ thành công.
Kin Shah

Câu trả lời:


10

Chọn sqlserver.read_only_route_completesự kiện mở rộng được đề cập bởi Kin và Remus, đây là một sự kiện Debug hay , nhưng nó không mang nhiều thông tin với nó - chỉ route_port(ví dụ: 1433) và route_server_name(ví dụ: sqlserver-0.contoso.com) theo mặc định . Điều này cũng chỉ giúp xác định khi nào kết nối mục đích chỉ đọc thành công. Có một read_only_route_failsự kiện nhưng tôi không thể kích hoạt nó, có thể nếu có sự cố với URL định tuyến, nó dường như không kích hoạt khi phiên bản phụ không khả dụng / tắt máy theo như tôi có thể nói.

Tuy nhiên, tôi đã có một số thành công khi tham gia với sqlserver.logintính năng theo dõi sự kiện và quan hệ nhân quả được kích hoạt, cùng với một số hành động (như sqlserver.username) để làm cho nó hữu ích.

Các bước để sinh sản

Tạo phiên Sự kiện mở rộng để theo dõi các sự kiện có liên quan, cộng với các hành động hữu ích và theo dõi quan hệ nhân quả:

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
)

Chạy phiên XE (xem xét lấy mẫu vì đây là sự kiện Gỡ lỗi) và thu thập một số thông tin đăng nhập:

kết nối sqlcmd

Lưu ý ở đây sqlserver-0 là thứ cấp dễ đọc của tôi và sqlserver-1 là chính. Ở đây tôi đang sử dụng công -Ktắc sqlcmdđể mô phỏng các thông tin đăng nhập mục đích ứng dụng chỉ đọc và một số thông tin đăng nhập SQL. Sự kiện chỉ đọc kích hoạt đăng nhập mục đích chỉ đọc thành công.

Khi tạm dừng hoặc dừng phiên tôi có thể truy vấn nó và cố gắng liên kết hai sự kiện, ví dụ:

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

Truy vấn sẽ hiển thị thông tin đăng nhập có và không có mục đích chỉ đọc ứng dụng:

Kết quả truy vấn

  • read_only_route_completelà một sự kiện Debug vì vậy hãy sử dụng một cách tiết kiệm. Xem xét lấy mẫu chẳng hạn.
  • hai sự kiện cùng với quan hệ nhân quả theo dõi cung cấp tiềm năng để thực hiện yêu cầu của bạn - thử nghiệm thêm cần thiết trên giàn khoan đơn giản này
  • Tôi đã thông báo nếu tên cơ sở dữ liệu không được chỉ định trong kết nối, mọi thứ dường như không hoạt động
  • Tôi đã cố gắng để có được pair_matchingmục tiêu để làm việc nhưng hết thời gian. Có một số tiềm năng để phát triển ở đây, đại loại như:

    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

Không, có vẻ như không có bất kỳ thuộc tính kết nối nào được hiển thị DMV (trong sys.dm_exec_connections hoặc sys.dm_exec_simes ) hoặc thậm chí CONNMENTPROPERTY liên quan đến ApplicationIntenttừ khóa ConnectionString.

Tuy nhiên, có thể đáng để yêu cầu, thông qua Microsoft Connect, rằng thuộc tính này được thêm vào sys.dm_exec_connectionsDMV vì nó dường như là một thuộc tính của kết nối được lưu trữ ở đâu đó trong bộ nhớ của SQL Server, dựa trên thông tin sau được tìm thấy trong trang MSDN cho Hỗ trợ SqlClient cho tính sẵn sàng cao, phục hồi thảm họa (mỏ nhấn mạnh in nghiêng):

Chỉ định ý định ứng dụng

Khi ApplicationIntent = ReadOnly , máy khách yêu cầu khối lượng công việc đọc khi kết nối với cơ sở dữ liệu luôn bật. Máy chủ sẽ thực thi ý định tại thời điểm kết nối và trong câu lệnh cơ sở dữ liệu USE nhưng chỉ với cơ sở dữ liệu luôn bật.

Nếu một USEtuyên bố có thể được xác minh, thì ApplicationIntentnhu cầu tồn tại ngoài nỗ lực kết nối ban đầu. Tuy nhiên, tôi chưa đích thân xác minh hành vi này.


Tái bút: Tôi đã nghĩ rằng chúng ta có thể sử dụng những sự thật rằng:

  • một bản sao chính có thể được thiết lập để không cho phép truy cập ReadOnly vào một hoặc nhiều cơ sở dữ liệu và
  • "ý định" sẽ được thực thi khi một USEcâu lệnh được thi hành.

Ý tưởng là tạo ra một Cơ sở dữ liệu mới chỉ với mục đích thử nghiệm và theo dõi cài đặt này. DB mới sẽ được sử dụng trong Nhóm sẵn có mới sẽ được đặt thành chỉ cho phép READ_WRITEkết nối. Lý thuyết là bên trong Trình kích hoạt đăng nhập, EXEC(N'USE [ReadWriteOnly]; INSERT INTO LogTable...;');một TRY...CATCHcấu trúc bên trong , về cơ bản không có gì trong CATCHkhối, sẽ không gây ra lỗi nào cho các kết nối ReadWrite (sẽ tự đăng nhập vào DB mới) hoặc USElỗi trên các kết nối ReadOnly, nhưng sau đó sẽ không có gì xảy ra vì lỗi bị bắt và bỏ qua (và INSERTtuyên bố sẽ không bao giờ đạt được). Trong cả hai trường hợp, sự kiện Đăng nhập thực tế sẽ không bị ngăn chặn / từ chối. Mã kích hoạt đăng nhập sẽ có hiệu quả là:

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;

Thật không may, khi kiểm tra hiệu quả của việc đưa ra một USEtuyên bố trong EXEC()một TRY...CATCHbên trong Giao dịch, tôi thấy rằng vi phạm quyền truy cập là hủy bỏ cấp độ hàng loạt, không phải là hủy bỏ cấp độ tuyên bố. Và thiết lập XACT_ABORT OFFkhông thay đổi bất cứ điều gì. Tôi thậm chí đã tạo một Thủ tục lưu trữ SQLCLR đơn giản để sử dụng Context Connection = true;và sau đó được gọi SqlConnection.ChangeDatabase()trong vòng a try...catchvà Giao dịch vẫn bị hủy bỏ. Và bạn không thể sử dụng Enlist=falsetrên Kết nối bối cảnh. Và việc sử dụng kết nối thường xuyên / bên ngoài trong SQLCLR để bước ra ngoài Giao dịch sẽ không giúp ích gì vì đây sẽ là một Kết nối hoàn toàn mới.

Có một khả năng rất nhỏ là HAS_DBACCESS có thể được sử dụng thay cho USEtuyên bố, nhưng tôi thực sự không có hy vọng cao về việc có thể kết hợp thông tin Kết nối hiện tại vào kiểm tra của mình. Nhưng tôi cũng không có cách nào để kiểm tra nó.

Tất nhiên, nếu có một Dấu vết có thể khiến vi phạm truy cập không bị hủy bỏ hàng loạt, thì kế hoạch được đề cập ở trên sẽ hoạt động ;-).


Thật không may, tôi không thể từ chối chúng - các bản sao có thể đọc được khác có thể bị hỏng. Tôi vẫn cần các truy vấn đọc để hoạt động trên chính - tôi chỉ cần biết khi nào chúng xảy ra.
Brent Ozar

@BrentOzar Tôi đã cập nhật câu trả lời của mình để bao gồm Bước 3 mới sẽ kiểm tra tình trạng đó và nếu không có Thứ hai có sẵn, thì nó sẽ cho phép Kết nối. Ngoài ra, nếu mục đích vẫn chỉ là "biết khi nào bạn đang xảy ra", thì có thể sử dụng cùng một thiết lập, chỉ cần thay đổi ROLLBACKKích hoạt đăng nhập INSERTthành một bảng nhật ký :-)
Solomon Rutzky

1
Đây là một câu trả lời tuyệt vời, nhưng nó không dành cho câu hỏi này. Tôi không cần dừng người dùng, tôi cần theo dõi khi nó xảy ra. Chúng tôi có các ứng dụng hiện có mà chúng tôi cần dần dần xác định và sửa chữa. Nếu tôi ngăn người dùng đăng nhập, nó sẽ gây ra một cuộc nổi loạn ngay lập tức. Nếu bạn muốn tạo một câu hỏi riêng cho câu hỏi này và đăng câu trả lời của bạn lên đó, điều đó thật tuyệt - nhưng vui lòng tập trung câu trả lời của bạn ở đây vào câu hỏi thực tế của tôi. Cảm ơn.
Brent Ozar

@BrentOzar Xin lỗi, tôi đã hiểu nhầm nhận xét của bạn với Tom là có nghĩa là một cái gì đó mạnh hơn một chút so với chỉ theo dõi / đăng nhập. Tôi đã loại bỏ phần câu trả lời của mình liên quan đến việc ngăn chặn truy cập.
Solomon Rutzky

@BrentOzar Tôi đã thêm một số ghi chú bên dưới dòng (trong phần PS) gần như là một giải pháp, nhưng bị cản trở ở cuối. Tôi đã đăng những ghi chú đó trong trường hợp nó nảy ra một ý tưởng trong bạn (hoặc ai đó) để đưa ra phần còn thiếu, hoặc thậm chí một cái gì đó hoàn toàn khác, có thể giải quyết câu đố này.
Solomon Rutzky

2

Bạn muốn bị bệnh như thế nào? Luồng TDS không khó để ủy quyền, chúng tôi đã làm điều đó cho ứng dụng SaaS của chúng tôi. Bit bạn đang tìm kiếm (nghĩa đen là một chút) có trong thông điệp login7. Bạn có thể yêu cầu người dùng của mình kết nối thông qua proxy và đăng nhập / thực thi bit ở đó. Địa ngục, bạn thậm chí có thể bật nó cho họ. :)


Điều đó chắc chắn là bệnh nặng hơn tôi muốn, nhưng cảm ơn, hahaha.
Brent Ozar

-1

Ứng dụng của bạn có sử dụng tài khoản dịch vụ hoặc có thể nhiều tài khoản dịch vụ không? Nếu vậy, hãy sử dụng Sự kiện mở rộng để theo dõi lưu lượng đăng nhập của bạn nhưng loại trừ tài khoản dịch vụ của bạn trên máy chủ luôn bật chính của bạn. Bây giờ bạn có thể thấy ai đang đăng nhập vào máy chủ luôn bật chính và không sử dụng chuỗi kết nối phụ chỉ đọc. Tôi đã sẵn sàng để cài đặt Luôn bật và đây là những gì tôi sẽ làm trừ khi bạn nói với tôi điều này sẽ không hoạt động.


1
Tom - giả định rằng người dùng có nhiều ứng dụng. Ví dụ: Bob kết nối với SQL Server Management Studio và với Excel. Anh ta kết nối với SSMS khi anh ta cần cập nhật và Excel khi anh ta cần đọc. Tôi cần đảm bảo rằng anh ấy đang sử dụng ApplicationIntent = ReadOnly khi anh ấy kết nối với Excel. (Đó không phải là kịch bản chính xác, nhưng nó đủ gần để minh họa.)
Brent Ozar

Tôi cũng có những người kết nối với máy chủ sản xuất của tôi với Excel với quyền truy cập rất hạn chế. Họ kết nối với quyền của họ. Tôi hy vọng tôi sẽ có thể nhìn thấy chúng. Chúng tôi sẽ sớm đưa chúng tôi lên.
ArmorDba

-1

Thật không may, tôi không có môi trường để kiểm tra những điều sau đây, và chắc chắn có một số điểm mà nó có thể thất bại, nhưng tôi sẽ ném nó ra ngoài vì những gì nó đáng giá.

Một thủ tục được lưu trữ CLR có quyền truy cập vào kết nối hiện tại thông qua new SqlConnection("context connection=true")cấu trúc (được lấy từ đây ). Kiểu SqlConnection hiển thị thuộc tính ConnectionString . Vì ApplicationIntent nằm trong chuỗi kết nối ban đầu, tôi cho rằng nó sẽ có sẵn trong thuộc tính này và có thể được phân tích cú pháp. Dĩ nhiên, có rất nhiều cơ hội trong chuỗi đó, rất nhiều cơ hội cho tất cả để biến thành hình quả lê.

Điều này sẽ chạy từ Trình kích hoạt đăng nhập và các giá trị bắt buộc vẫn tồn tại khi cần.


1
Điều này sẽ không làm việc. Mã SQLCLR không có quyền truy cập vào Kết nối hiện tại, nó có quyền truy cập vào Phiên hiện tại thông qua Kết nối bối cảnh. Đối tượng SqlConnection trong mã .NET không chạm vào kết nối thực tế được tạo từ phần mềm máy khách gốc vào SQL Server. Đó là hai điều riêng biệt.
Solomon Rutzky

Oh tốt, đừng bận tâm.
Michael Green

Không, điều này không hoạt động.
Brent Ozar
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.