在不使用触发器的情况下,在SQL Server中查找触发查询的客户端的身份?


11

我目前正在使用Change Data Capture(CDC)来跟踪数据更改,并且希望跟踪提交进行了更改的查询的客户端的主机名和IP地址。如果有5个不同的客户端通过相同的用户名登录,则一个客户端将面临跟踪5个客户端中的哪一个引发查询的难题。我发现的其他可能的解决方案包括使用以下命令更改CDC表:

ALTER TABLE cdc.schema_table_CT 
ADD HostName nvarchar(50) NULL DEFAULT(HOST_NAME())

但是,这将返回在其上触发查询的服务器的主机名,而不是触发该查询的客户端的主机名。

有办法解决这个问题吗?这将有助于记录客户端的主机名或IP地址(或其他唯一身份)。我不想使用触发器,因为它会减慢系统速度,而且CDC还会生成系统表,因此显然不可能在上面设置触发器。

Answers:


4

我不确定CDC,但是如果登录成功view server state permission,则可以使用DMV获取一些信息。

这在此处的联机丛书中提供。我将查询更改为添加列,这将为您提供IP address

SELECT 
    c.session_id, c.net_transport, c.encrypt_option, c.auth_scheme,
    s.host_name, s.program_name, s.client_interface_name,
    c.local_net_address, c.client_net_address, s.login_name, s.nt_domain, 
    s.nt_user_name, s.original_login_name, c.connect_time, s.login_time 
FROM sys.dm_exec_connections AS c
JOIN sys.dm_exec_sessions AS s
    ON c.session_id = s.session_id
WHERE c.session_id = SPID;  --session ID you want to track

4

当您说“不使用触发器”时,您是指表上的任何触发器还是逐行触发器?

我问是因为您可以通过明智地使用该CONTEXT_INFO()函数来获得所需的内容,但是您需要确保SET CONTEXT_INFO在执行操作之前正确调用了该函数。

一个做到这一点的地方可能是服务器级别的登录触发器(即不是数据库/对象级别的触发器),如下所示:

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

然后可以将默认约束添加到表中以存储上下文(以提高插入速度):

ALTER TABLE cdc.schema_table_CT 
ADD ContextInfo varbinary(128) NULL DEFAULT(CONTEXT_INFO())

一旦有了,就可以用一刀切的方式查询该ContextInfo列:

SELECT *
    ,spid = CONVERT(INT, SUBSTRING(ContextInfo, 1, 4))
    ,client = CONVERT(VARCHAR(64), SUBSTRING(ContextInfo, 5, 64))
FROM cdc.schema_table_CT

从技术上讲,你能做到这一点SUBSTRINGCONVERT东西作为默认约束的一部分,并且只存储客户端IP存在,但它可能会更快存储整个上下文中出现(因为它是在每一个完成的INSERT),并且只提取一个值SELECT当您需要它们时。

我可能倾向于将所有我的调用SUBSTRINGCONVERT调用包装在一个单行内联表值函数中,CROSS APPLY必要时将使用它。这样可以将拆包逻辑放在一个地方:

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

请注意,CONTEXT_INFO只有128个字节VARBINARY。如果您需要的数据超出了128字节的容纳空间,我将创建一个表来保存所有数据,在登录触发器中将该行作为“会话”的行插入表中,并设置CONTEXT_INFO为该表的替代键值

您还应注意,由于这只是默认约束,因此对于具有适当特权的用户而言,覆盖静态表中的上下文数据是微不足道的。当然,“ audit”样式表中的所有其他列也是如此。

如果它可以是一个持久化的计算列,而不是默认列,那将是很好的选择,但是该CONTEXT_INFO()函数是不确定的,因此是不可行的(您可能可以FUNCTION在a周围使用一些技巧VIEW,但是我不会)。

对于具有足够访问权限的用户来说,调用SET CONTEXT_INFO自己并弄乱您的一天(例如,使用伪造的值或特制的存储进样)也很琐碎,因此请谨慎对待内容,在显示之前对其进行编码,并处理异常好。

至于主机名,我认为ClientHost元素EVENTDATA()为您提供IP地址(或<local machine>指示符)。从技术上讲,您可以使用CLR将DNS反向查找回主机名,但对于每个主机来说,这样做往往太慢了INSERT,因此我建议要这样做。

如果必须具有主机名,则可能需要使用SQL代理作业定期使用本地DHCP服务器或DNS区域文件中的当前租约(作为带外进程)填充一个单独的表,并LEFT JOIN使用将来的查询(或包装标量FUNCTION以为默认约束提供时间点的值)。

同样,您应该注意,如果应用程序具有任何面向公众的组件,则IP地址和主机名将不可靠(例如,由于NAT)。即使它不是面向公众的,但对于大多数IP /主机名映射,还是有一些基于时间的组件可能需要考虑在内。

最后,在实施登录触发器之前,可能有必要打开服务器的专用管理员连接。如果登录触发器以任何方式中断,它可以阻止所有用户登录(包括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

如果确实被锁定,则可以使用DAC删除或禁用登录触发器:

C:\> sqlcmd -S localhost -d master -A
1> DISABLE TRIGGER tr_audit_login ON ALL SERVER
2> GO

3

请看一下connect bug:以下是它的相关代码段

此行为是设计使然。CDC旨在公开有关更改的以下信息:更新的列,操作类型和事务信息。尚未将其设计为审核解决方案。它的创建旨在通过增量数据加载实现有效的提取传输和加载解决方案(ETL),这是减少总体ETL时间的关键。它的主要目标是在什么时候公开“已更改的内容”而不是谁。为此,我建议使用SQL Audit功能。

到目前为止,还没有计划在审核解决方案中转换CDC。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.