获取在UPDATE触发器上运行SQL查询的作业

时间:2014-05-14 13:19:18

标签: sql sql-server triggers ssis sql-server-job

我正在尝试为在Web应用程序,SQL Server代理作业中执行的操作创建审计跟踪,并手动运行对数据库的查询。我试图使用触发器来捕获某些表的更新,插入和删除。

整个过程正在发挥作用。例如,用户在Web应用程序中执行更新,触发器将更新的数据写入我已定义的审计跟踪表,包括执行操作的人员的用户名。从Web应用程序或手动查询角度来看,这样可以正常工作,但我们还有许多SQL Server代理作业,我想捕获哪些作业运行特定查询。每个代理作业都使用相同的用户名运行。这也正常工作,并正确输入用户名到表中,但我找不到哪个作业调用此查询。

我当前的“解决方案”是在触发时查找当前正在运行的作业,因为其中一个必须是正确的。使用:

CREATE TABLE #xp_results 

    ( 
    job_id                UNIQUEIDENTIFIER NOT NULL,   
    last_run_date         INT              NOT NULL,   
    last_run_time         INT              NOT NULL,   
    next_run_date         INT              NOT NULL,   
    next_run_time         INT              NOT NULL,   
    next_run_schedule_id  INT              NOT NULL,   
    requested_to_run      INT              NOT NULL, -- BOOL   
    request_source        INT              NOT NULL,   
    request_source_id     sysname          COLLATE database_default NULL,   
    running               INT              NOT NULL, -- BOOL   
    current_step          INT              NOT NULL,   
    current_retry_attempt INT              NOT NULL,   
    job_state             INT              NOT NULL
    )   

INSERT INTO  #xp_results  
EXECUTE master.dbo.xp_sqlagent_enum_jobs 1, 'sa'  
SELECT @runningJobs = STUFF((SELECT ',' + j.name 
                FROM #xp_results r
                INNER JOIN msdb..sysjobs j ON r.job_id = j.job_id
                WHERE running = 1
                FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')

DROP TABLE #xp_results

我运行了一个特定的工作来测试它似乎工作,因为正在运行的任何其他工作将列在@runningJobs中,但它不记录运行它的工作。我假设在触发器运行时,作业已经完成。

有没有办法可以找出哪个作业调用触发触发器的查询?

编辑:我尝试更改上面的SELECT查询,以获取在过去2分钟内运行或当前正在运行的任何作业。 SQL查询现在是:

SELECT @runningJobs = STUFF((SELECT ',' + j.name 
            FROM #xp_results r
            INNER JOIN msdb..sysjobs j ON r.job_id = j.job_id
            WHERE (last_run_date = CAST(REPLACE(LEFT(CONVERT(VARCHAR, getdate(), 120), 10), '-', '') AS INT)
            AND last_run_time > CAST(REPLACE(LEFT(CONVERT(VARCHAR,getdate(),108), 8), ':', '') AS INT) - 200)
            OR running = 1
            FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')

当我运行作业时,在作业运行时运行上述查询,返回正确的作业。但是,当通过SQL Server代理作业运行SSIS包或在SSIS中手动运行时,@runningJobs不会填充,只返回NULL

所以我现在认为这是SSIS和master.dbo.xp_sqlagent_enum_jobs权限的问题。还有其他想法吗?

编辑#2 :实际上不要认为这是权限错误。此代码下面有一个INSERT语句,如果它是权限错误,则INSERT语句不会运行,因此审计行不会添加到数据库中。因此,由于在数据库中添加了一行,而不是填充了runningJobs字段。奇怪的时候。

编辑#3 :我只想澄清,我正在寻找一个解决方案,不要求我进入每项工作并改变任何事情。有太多的工作要做,这是一个可行的解决方案。

3 个答案:

答案 0 :(得分:2)

工作代码是第一次编辑 - ( anothershrubery

使用审核触发器中的app_name()函数http://msdn.microsoft.com/en-us/library/ms189770.aspx获取运行查询的应用程序的名称。

对于SQL代理作业,app_name在应用程序名称中包含作业步骤ID(如果是 T-SQL 步骤)。我们在审计触发器中执行此操作并且运行良好。从审计触发器中运行时app_name()结果的示例:

  

SQLAgent - TSQL JobStep(作业0x96EB56A24786964889AB504D9A920D30:步骤   1)

可以通过job_id中的msdb.dbo.sysjobs_view列查找此作业。

由于SSIS包在SQL Agent作业引擎之外启动SQL连接,因此这些连接将具有自己的应用程序名称,您需要在SSIS包的连接字符串中设置应用程序名称。在SSIS包,Web应用程序,WinForms或连接到SQL Server的任何客户端中,您可以在连接字符串中使用此设置app_name函数返回的值:

"Application Name=MyAppNameGoesHere;" 

http://www.connectionstrings.com/use-application-name-sql-server/

如果未在.NET连接字符串中设置“应用程序名称”,则使用System.Data.SqlClient.SqlConnection时的默认值为“.Net SqlClient数据提供程序”。

通常用于审核的其他一些字段:

以下是用于设置/获取上下文信息的SQL帮助程序方法:

CREATE PROC dbo.usp_ContextInfo_SET
    @val varchar(128)
as
begin
    set nocount on;
    DECLARE @c varbinary(128);
    SET @c=cast(@val as varbinary(128));
    SET CONTEXT_INFO @c;
end
GO

CREATE FUNCTION [dbo].[ufn_ContextInfo_Get] ()
RETURNS varchar(128)
AS
BEGIN
    --context_info is binary data type, so will pad any values will CHAR(0) to the end of 128 bytes, so need to replace these with empty string.
    RETURN REPLACE(CAST(CONTEXT_INFO() AS varchar(128)), CHAR(0), '')
END

修改

app_name()是获取查询中涉及的应用程序的首选方法,但是由于您不想更新任何SSIS包,因此这是一个更新的查询,以使用以下方法获取当前正在执行的作业记录的SQL代理表。您可能必须在msdb数据库中为这些表调整GRANTs for SELECT以使查询成功,或者使用此查询创建视图,并调整该视图的授权。

查询:

;with cteSessions as
(
    --each time that SQL Agent is started, a new record is added to this table.
    --The most recent session is the current session, and prior sessions can be used 
    --to identify the job state at the time that SQL Agent is restarted or stopped unexpectedly
    select top 1 s.session_id
    from msdb.dbo.syssessions s
    order by s.agent_start_date desc
)
SELECT runningJobs =
    STUFF(
    (   SELECT N', [' + j.name + N']'
        FROM msdb.dbo.sysjobactivity a
            inner join cteSessions s on s.session_id = a.session_id
            inner join msdb.dbo.sysjobs j on a.job_id = j.job_id
            left join msdb.dbo.sysjobhistory h2 on h2.instance_id = a.job_history_id
        WHERE 
            --currently executing jobs:
            h2.instance_id is null
            AND a.start_execution_date is not null
            AND a.stop_execution_date is null
        ORDER BY j.name
        FOR XML PATH(''), ROOT('root'), TYPE
    ).query('root').value('.', 'nvarchar(max)') --convert the xml to nvarchar(max)
    , 1, 2, '') -- replace the leading comma and space with empty string.
;

编辑#2:

此外,如果您使用的是SQL 2012或更高版本,请检查SSISDB.catalog.executions视图http://msdn.microsoft.com/en-us/library/ff878089(v=sql.110).aspx以获取当前运行的SSIS包列表,无论它们是否是从预定作业中启动的。我没有在2012年之前的SQL Server版本中看到过等效的视图。

答案 1 :(得分:0)

我会在您的表格中添加一个额外的列,例如Update_Source,并获取所有源应用程序(包括SSIS)以在更新表时进行设置。

您可以将USER用作该列的DEFAULT,以最大限度地减少所需的更改。

答案 2 :(得分:0)

您可以尝试使用CONTEXT_INFO

尝试将SET CONTEXT_INFO 'A Job'的T-SQL步骤添加到您的作业中

然后尝试使用sys.dm_exec_sessions

在触发器中读取该内容

我很想知道它是否有效 - 请发表你的发现。

http://msdn.microsoft.com/en-us/library/ms187768(v=sql.105).aspx