Khi bạn nói, "không sử dụng kích hoạt", bạn có nghĩa là bất kỳ kích hoạt hoặc chỉ kích hoạt từng hàng trên bảng?
Tôi yêu cầu bởi vì bạn có thể có được những gì bạn muốn với việc sử dụng CONTEXT_INFO()
chức năng một cách thận trọng , nhưng bạn sẽ cần đảm bảo rằng nó SET CONTEXT_INFO
được gọi chính xác trước khi hoạt động của bạn diễn ra.
Một nơi để làm điều đó có thể là trình kích hoạt đăng nhập cấp máy chủ (nghĩa là không phải là trình kích hoạt cơ sở dữ liệu / mức đối tượng), như vậy:
USE master
GO
CREATE TRIGGER tr_audit_login
ON ALL SERVER
WITH EXECUTE AS 'sa'
AFTER LOGON
AS BEGIN
BEGIN TRY
DECLARE @eventdata XML = EVENTDATA();
IF @eventdata IS NOT NULL BEGIN
DECLARE @spid INT;
DECLARE @client_host VARCHAR(64);
SET @client_host = @eventdata.value('(/EVENT_INSTANCE/ClientHost)[1]', 'VARCHAR(64)');
SET @spid = @eventdata.value('(/EVENT_INSTANCE/SPID)[1]', 'INT');
-- pack the required data into the context data binary
-- (spid is just an example of packing multiple data items in a single field: you would probably use @@SPID at the point of use, instead)
DECLARE @context_data VARBINARY(128);
SET @context_data = CONVERT(VARBINARY(4), @spid)
+ CONVERT(VARBINARY(64), @client_host);
-- persist the spid and host into session-level memory
SET CONTEXT_INFO @context_data;
END
END TRY
BEGIN CATCH
/* do better error handling here...
* logon trigger can lock all users out of server, so i am just swallowing everything
*/
DECLARE @msg NVARCHAR(4000) = ERROR_MESSAGE();
RAISERROR('%s', 10, 1, @msg) WITH LOG;
END CATCH
END
Sau đó, bạn có thể thêm các ràng buộc mặc định vào bảng của mình, để lưu trữ ngữ cảnh (đối với tốc độ chèn):
ALTER TABLE cdc.schema_table_CT
ADD ContextInfo varbinary(128) NULL DEFAULT(CONTEXT_INFO())
Khi bạn đã có điều đó, bạn có thể truy vấn ContextInfo
cột đó với một chút xúc xắc:
SELECT *
,spid = CONVERT(INT, SUBSTRING(ContextInfo, 1, 4))
,client = CONVERT(VARCHAR(64), SUBSTRING(ContextInfo, 5, 64))
FROM cdc.schema_table_CT
Về mặt kỹ thuật, bạn có thể làm điều đó SUBSTRING
và xử lý CONVERT
như một phần của ràng buộc mặc định của mình và chỉ lưu trữ IP của máy khách ở đó, nhưng có thể nhanh hơn để lưu trữ toàn bộ bối cảnh ở đó (như được thực hiện trên mọi INSERT
) và chỉ trích xuất các giá trị trong một SELECT
khi bạn cần chúng
Tôi có thể có xu hướng bọc tất cả các cuộc gọi của tôi SUBSTRING
và CONVERT
trong một hàm có giá trị bảng nội tuyến một hàng, mà tôi sẽ làm CROSS APPLY
khi cần thiết. Điều đó giữ logic giải nén ở một nơi:
CREATE FUNCTION fn_context (
@context_info VARBINARY(128)
)
RETURNS TABLE
AS RETURN (
SELECT
spid = CONVERT(INT, SUBSTRING(@context_info, 1, 4))
,client = CONVERT(VARCHAR(64), SUBSTRING(@context_info, 5, 64))
)
GO
SELECT *
FROM cdc.schema_table_CT s
CROSS APPLY dbo.fn_context(s.ContextInfo) c
Lưu ý rằng đó CONTEXT_INFO
chỉ là 128 byte VARBINARY
. Nếu bạn cần nhiều dữ liệu hơn mức bạn có thể chứa 128 byte, tôi sẽ tạo một bảng để chứa tất cả dữ liệu đó, chèn dưới dạng hàng cho 'phiên' đó vào bảng trong trình kích hoạt đăng nhập và đặt thành CONTEXT_INFO
giá trị khóa thay thế của bảng đó
Bạn cũng nên lưu ý rằng, vì nó chỉ là một ràng buộc mặc định, nên người dùng có đặc quyền phù hợp sẽ ghi đè lên dữ liệu ngữ cảnh đó trong bảng at-rest. Tất nhiên, điều tương tự cũng đúng với tất cả các cột khác trong các bảng kiểu 'kiểm toán'.
Sẽ thật tuyệt nếu nó có thể là một cột được tính toán bền vững, thay vì mặc định, nhưng CONTEXT_INFO()
chức năng này không mang tính quyết định, vì vậy nó là không nên (bạn có thể sử dụng một số FUNCTION
mánh khóe xung quanh VIEW
, nhưng tôi sẽ không ).
Nó cũng không quan trọng đối với người dùng đó có đủ quyền truy cập để SET CONTEXT_INFO
tự gọi và làm xáo trộn ngày của bạn (ví dụ: với các giá trị giả hoặc tiêm lưu trữ được chế tạo đặc biệt), vì vậy hãy đối xử với sự nghi ngờ và quan tâm, mã hóa nó trước khi hiển thị và xử lý các trường hợp ngoại lệ tốt.
Đối với tên máy chủ, tôi nghĩ ClientHost
yếu tố EVENTDATA()
cung cấp cho bạn địa chỉ IP (hoặc <local machine>
chỉ báo). Mặc dù về mặt kỹ thuật bạn có thể sử dụng CLR để thực hiện tra cứu DNS ngược trở lại tên máy chủ, nhưng chúng có xu hướng quá chậm để thực hiện INSERT
, vì vậy tôi khuyên bạn không nên làm như vậy.
Nếu bạn phải có tên máy chủ, bạn có thể muốn sử dụng công việc Tác nhân SQL để định kỳ điền vào một bảng riêng với các hợp đồng thuê hiện tại từ máy chủ DHCP cục bộ hoặc tệp vùng DNS, như một quy trình ngoài băng tần và LEFT JOIN
trong đó các truy vấn trong tương lai (hoặc bọc trong một vô hướng FUNCTION
để cung cấp một giá trị cho một ràng buộc mặc định, theo thời gian).
Một lần nữa, bạn cần lưu ý rằng, nếu ứng dụng có bất kỳ loại thành phần nào đối mặt với công chúng, địa chỉ IP và tên máy chủ không đáng tin cậy (ví dụ do NAT). Ngay cả khi nó không phải là đối tượng công khai, vẫn có một thành phần dựa trên thời gian nhất định đối với hầu hết các bản đồ IP / tên máy chủ, mà bạn có thể cần phải tính đến.
Cuối cùng, trước khi thực hiện kích hoạt đăng nhập của bạn, có thể đáng để bật kết nối quản trị viên chuyên dụng của máy chủ của bạn. Nếu kích hoạt đăng nhập bị hỏng theo bất kỳ cách nào, nó có thể ngăn tất cả người dùng đăng nhập (bao gồm cả tài khoản sysadmin):
USE master
GO
-- you may want to do this, so you have a back-out if the login trigger breaks login
EXEC sp_configure 'remote admin connections', 1
GO
RECONFIGURE
GO
Nếu bạn bị khóa, có thể sử dụng DAC để thả hoặc vô hiệu hóa trình kích hoạt đăng nhập:
C:\> sqlcmd -S localhost -d master -A
1> DISABLE TRIGGER tr_audit_login ON ALL SERVER
2> GO