我正在寻找一种很好的方法来记录在我的SQL Server 2005数据库中的一组特定表上发生的更改。我认为最好的方法是通过触发器来执行更新和删除操作。无论如何都要抓住正在运行的实际语句?一旦我有了声明,我就可以轻松地将其记录到其他地方(其他数据库表)。但是,我还没有找到一种简单的方法(如果可能的话)来获取正在运行的SQL语句。
答案 0 :(得分:7)
如果您只想在某些数据库表中保留所有事务的日志(插入,更新和删除),则可以运行以下脚本:
IF NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME= 'Audit')
CREATE TABLE LogTable
(
LogID [int]IDENTITY(1,1) NOT NULL,
Type char(1),
TableName varchar(128),
PrimaryKeyField varchar(1000),
PrimaryKeyValue varchar(1000),
FieldName varchar(128),
OldValue varchar(1000),
NewValue varchar(1000),
UpdateDate datetime DEFAULT (GetDate()),
UserName varchar(128)
)
GO
DECLARE @sql varchar(8000), @TABLE_NAME sysname
SET NOCOUNT ON
SELECT @TABLE_NAME= MIN(TABLE_NAME)
FROM INFORMATION_SCHEMA.Tables
WHERE
--query for table that you want to audit
TABLE_TYPE= 'BASE TABLE'
AND TABLE_NAME!= 'sysdiagrams'
AND TABLE_NAME!= 'LogTable'
AND TABLE_NAME!= 'one table to not record de log';
WHILE @TABLE_NAME IS NOT NULL
BEGIN
SELECT 'PROCESANDO ' + @TABLE_NAME;
EXEC('IF OBJECT_ID (''' + @TABLE_NAME+ '_ChangeTracking'', ''TR'') IS NOT NULL DROP TRIGGER ' + @TABLE_NAME+ '_ChangeTracking')
SELECT @sql = 'create trigger ' + @TABLE_NAME+ '_ChangeTracking on ' + @TABLE_NAME+ ' for insert, update, delete
as
declare
@bit int ,
@field int ,
@maxfield int ,
@char int ,
@fieldname varchar(128) ,
@TableName varchar(128) ,
@PKCols varchar(1000) ,
@sql varchar(2000),
@UpdateDate varchar(21) ,
@UserName varchar(128) ,
@Type char(1) ,
@PKFieldSelect varchar(1000),
@PKValueSelect varchar(1000)
select @TableName = ''' + @TABLE_NAME+ '''
-- date and user
select @UserName = system_user ,
@UpdateDate = convert(varchar(8), getdate(), 112) + '' '' + convert(varchar(12), getdate(), 114)
-- Action
if exists (select * from inserted)
if exists (select * from deleted)
select @Type = ''U''
else
select @Type = ''I''
else
select @Type = ''D''
-- get list of columns
select * into #ins from inserted
select * into #del from deleted
-- Get primary key columns for full outer join
select @PKCols = coalesce(@PKCols + '' and'', '' on'') + '' i.'' + c.COLUMN_NAME + '' = d.'' + c.COLUMN_NAME
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
where pk.TABLE_NAME = @TableName
and CONSTRAINT_TYPE = ''PRIMARY KEY''
and c.TABLE_NAME = pk.TABLE_NAME
and c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
-- Get primary key fields select for insert(comma deparated)
select @PKFieldSelect = coalesce(@PKFieldSelect+''+'','''') + '''''''' + COLUMN_NAME + '',''''''
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
where pk.TABLE_NAME = @TableName
and CONSTRAINT_TYPE = ''PRIMARY KEY''
and c.TABLE_NAME = pk.TABLE_NAME
and c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
-- Get primary key values for insert(comma deparated as varchar)
select @PKValueSelect = coalesce(@PKValueSelect+''+'','''') + ''convert(varchar(100), coalesce(i.'' + COLUMN_NAME + '',d.'' + COLUMN_NAME + ''))'' + ''+'''',''''''
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
where pk.TABLE_NAME = @TableName
and CONSTRAINT_TYPE = ''PRIMARY KEY''
and c.TABLE_NAME = pk.TABLE_NAME
and c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
if @PKCols is null
begin
raiserror(''no PK on table %s'', 16, -1, @TableName)
return
end
select @sql = ''insert LogTable(Type, TableName, PrimaryKeyField, PrimaryKeyValue, UserName)''
select @sql = @sql + '' select '''''' + @Type + ''''''''
select @sql = @sql + '','''''' + @TableName + ''''''''
select @sql = @sql + '','' + @PKFieldSelect
select @sql = @sql + '','' + @PKValueSelect
select @sql = @sql + '','''''' + @UserName + ''''''''
select @sql = @sql + '' from #ins i full outer join #del d''
select @sql = @sql + @PKCols
exec (@sql)
';
SELECT @sql
EXEC(@sql)
SELECT @TABLE_NAME= MIN(TABLE_NAME) FROM INFORMATION_SCHEMA.Tables
WHERE TABLE_NAME> @TABLE_NAME
--query for table that you want to audit
AND TABLE_TYPE= 'BASE TABLE'
AND TABLE_NAME!= 'sysdiagrams'
AND TABLE_NAME!= 'LogTable'
AND TABLE_NAME!= 'one table to not record de log';
END
答案 1 :(得分:5)
您应该可以使用system management views完成此操作。
一个例子是这样的:
SELECT er.session_id,
er.status,
er.command,
DB_NAME(database_id) AS 'DatabaseName',
user_id,
st.text
FROM sys.dm_exec_requests AS er
CROSS APPLY sys.dm_exec_sql_text(er.sql_handle) AS st
WHERE er.session_id = @@SPID;
我不确定这对你有用,因为更多以数据为中心的日志记录机制可能会这样。
答案 2 :(得分:4)
不要忘记您的日志记录将成为事务的一部分,因此如果出现错误并回滚事务,您的日志也将被删除。
答案 3 :(得分:2)
答案 4 :(得分:2)
有一种模式可用于创建名为日志触发器的此类触发器。这是独立于供应商的,非常简单。它在here中描述。
更改记录在另一个历史记录表中。没有办法获取确切的语句,但可以检测它是否是插入,更新或删除,因为它创建了一个“链式”记录集。插入是没有前任的记录,删除是没有后继的记录,中间记录是更新。可以检测到记录与其前任记录的比较。
在给定的时间点获取单个实体(或整个表)的快照非常容易。
作为奖励,与Oracle,DB2和MySQL相比,SQL Server的这种模式的语法恰好是最简单的。
答案 5 :(得分:1)
您是否真的需要记录运行的语句,大多数人都会记录更改的数据(触发器中的INSERTED和DELETED表)。
答案 6 :(得分:1)
触发器很糟糕,我会远离触发器。
如果您尝试对某些内容进行问题排查,请将Sql Profiler附加到具有特定条件的数据库中。这将记录每次查询运行以供检查。
另一种选择是更改为调用程序以记录其查询。这是一种非常普遍的做法。
答案 7 :(得分:1)
触发器是确保记录任何更改的好方法,因为无论更新如何执行,它们几乎总是会触发 - 例如ad-hoc连接以及应用程序连接。
正如@mwigdahl所建议的那样,系统管理视图看起来是捕获当前运行批处理的好方法。这对于登录触发器是否特别有用是另一回事。
使用触发器的一个缺点是您只能从数据库连接中识别更新源。许多应用程序没有与连接关联的任何用户信息,以便于连接池,因此您不知道哪个用户正在执行该操作。即,连接使用的登录是通用应用程序登录,而不是使用该应用程序的人。解决此问题的常用方法是使用存储过程作为所有数据库交互的接口,然后确保UserId与所有过程调用一起传递。然后,您可以通过存储过程而不是触发器执行日志记录。显然,只有当您知道人们不使用这些程序直接更新表,或者不需要记录这种情况时,这才有用。
获取当前正在执行的批处理的能力可能提供更好的机制:如果您确保所有sql批处理包含UserId,则可以从触发器中的sql中提取它。这将允许您使用触发器执行所有日志记录,这意味着您捕获所有内容,但也允许您将更改与特定用户相关联。
如果您正在按下触发路径,那么值得检查情况触发器是否被触发(可能是批量加载数据?或者如果人们有权禁用触发器)。
同时考虑@idstam指出触发器代码将在您的事务中,因此通常会记录并随之回滚。
编写触发器时要考虑的另一件事是@@IDENTITY的行为:如果你有使用@@ IDENTITY的程序,你可能会意外地改变他们的行为。
答案 8 :(得分:1)
没有理由捕获实际的SQL,因为可以有许多不同的语句以相同的方式更改数据。
答案 9 :(得分:0)
尝试安装一些基于触发器的第三方工具,例如ApexSQL Audit,然后对它们的执行方式进行逆向工程。只需将其安装在试用模式下,看看它是如何生成触发器以捕获所有不同类型的信息的。
需要考虑的其他几个方面是:
存储规划 - 如果您有大量更新,则意味着您将获得大量审计数据。我考虑将这些数据存储在不同的数据库中。特别是如果您计划审核多个数据库。
管理数据量 - 随着时间的推移,您可能不需要保留一些非常旧的记录。计划轻松删除旧数据
架构更改 - 如果更新架构会怎样。在最坏的情况下,如果没有正确创建,您的触发器将停止工作并抛出错误。在最好的情况下,你会错过一些数据。这也是需要考虑的事情。
考虑到所有这些因素,与一些已经开发的解决方案相比,这可能是最有效的时间,而不是自己从头开始创建。
答案 10 :(得分:0)
这是根据Juan Carlos Velez的回答改编的。我对其进行了修改,以解决复合主键以及包含空格的列名的问题。另外,我在全文中都进行了注释,以便希望对其目的进行修改的人可以理解每个步骤(如果他们对代码不清楚的话)的情况。
-- This stops the message that shows the count of the number of rows affected from being returned as part of the result set.
set nocount on
-- If the Audit table doesn't exist, create it.
if not exists(select * from INFORMATION_SCHEMA.TABLES where TABLE_NAME = 'Audit')
create table Audit
(
AuditID [int] identity(1,1) not null,
[Type] char(1),
TableName nvarchar(128),
PKFields nvarchar(max),
PKValues nvarchar(max),
FieldName nvarchar(128),
OldValue nvarchar(max),
NewValue nvarchar(max),
UpdateDate datetime,
UserName nvarchar(128)
)
go
-- Variables for the dynamic SQL and table name.
declare @tr nvarchar(max),
@tableName sysname
-- Get the first table in database. Skip over views and a few specified tables.
select @tableName = min(TABLE_NAME) from INFORMATION_SCHEMA.TABLES where TABLE_TYPE = 'BASE TABLE' and TABLE_NAME <> 'sysdiagrams' and TABLE_NAME <> 'Audit'
---- If you want to specify certain tables, uncomment the next line and add your table names.
--and (TABLE_NAME = 'IGAs' or TABLE_NAME = 'IGABudgets' or TABLE_NAME = 'Resolutions' or TABLE_NAME = 'RTAProjects' or TABLE_NAME = 'RTASubProjects')
-- Loop through the tables in the database and create an audit trigger on each one.
while @tableName is not null
begin
-- If a trigger of the same name already exists, delete it.
exec('if OBJECT_ID (''' + @tableName + '_ChangeTracking'', ''TR'') is not null drop trigger ' + @tableName + '_ChangeTracking')
-- Check if there is a primary key. If not, throw an error.
if (select count(*) from INFORMATION_SCHEMA.TABLE_CONSTRAINTS c, INFORMATION_SCHEMA.KEY_COLUMN_USAGE u where c.TABLE_NAME = @tableName and c.CONSTRAINT_TYPE = 'PRIMARY KEY' and u.TABLE_NAME = c.TABLE_NAME and u.CONSTRAINT_NAME = c.CONSTRAINT_NAME) = 0
begin
raiserror('Error: There is no primary key on table %s', 16, -1, @tableName)
return
end
-- Create the trigger.
select @tr = 'create trigger ' + @tableName + '_ChangeTracking on ' + @tableName + ' for insert, update, delete as
-- Misc variables.
declare @table nvarchar(128),
@fieldName nvarchar(128) = '''',
@type char(1),
@pkJoin nvarchar(max),
@pkSelect nvarchar(max),
@pkFields nvarchar(max),
@pkValues nvarchar(max),
@updateDate nvarchar(30) = convert(varchar(30), getdate(), 22),
@user nvarchar(128) = system_user,
@sql nvarchar(max),
@params nvarchar(max) = N''@out nvarchar(max) output'',
@fieldIndex int = 0,
@maxField int,
@bit int,
@char int
-- Get the table name.
select @table = object_name(parent_id) from sys.triggers where object_id = @@PROCID
-- Get the modification type: U = update, I = insert, D = delete
if exists (select * from inserted)
if exists (select * from deleted)
select @type = ''U''
else select @type = ''I''
else select @type = ''D''
-- Save the inserted and deleted values into temp tables.
select * into #ins from inserted
select * into #del from deleted
-- Get the number of columns in the table.
select @maxField = max(columnproperty(object_id(@table), COLUMN_NAME, ''ColumnID'')) from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = @table
-- Get the primary key join relationship(s).
select @pkJoin = coalesce(@pkJoin + '' and'', '' on'') + '' i.['' + u.COLUMN_NAME + ''] = d.['' + u.COLUMN_NAME + '']''
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS c, INFORMATION_SCHEMA.KEY_COLUMN_USAGE u
where c.TABLE_NAME = @table
and c.CONSTRAINT_TYPE = ''PRIMARY KEY''
and u.TABLE_NAME = c.TABLE_NAME
and u.CONSTRAINT_NAME = c.CONSTRAINT_NAME
-- Get the primary key field name(s).
select @pkFields = coalesce(@pkFields + '', '', '''') + ''['' + u.COLUMN_NAME + '']''
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS c, INFORMATION_SCHEMA.KEY_COLUMN_USAGE u
where c.TABLE_NAME = @table
and c.CONSTRAINT_TYPE = ''PRIMARY KEY''
and u.TABLE_NAME = c.TABLE_NAME
and u.CONSTRAINT_NAME = c.CONSTRAINT_NAME
-- Get the primary key field(s) for select statement.
select @pkSelect = coalesce(@pkSelect + '' + '''', '''' + '', '''') + ''convert(nvarchar(max), ['' + u.COLUMN_NAME + ''])''
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS c, INFORMATION_SCHEMA.KEY_COLUMN_USAGE u
where c.TABLE_NAME = @table
and c.CONSTRAINT_TYPE = ''PRIMARY KEY''
and u.TABLE_NAME = c.TABLE_NAME
and u.CONSTRAINT_NAME = c.CONSTRAINT_NAME
-- Get the primary key field value(s).
if (@type = ''D'')
begin
set @sql = ''select @out = '' + @pkSelect + '' from #del''
exec sp_executesql @sql, @params, @out = @pkValues output
end
else
begin
set @sql = ''select @out = '' + @pkSelect + '' from #ins''
exec sp_executesql @sql, @params, @out = @pkValues output
end
-- Loop through each field in the inserted table.
while @fieldIndex < @maxField
begin
-- Iterate the fieldIndex.
select @fieldIndex = min(ORDINAL_POSITION) from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = @table and columnproperty(object_id(@table), COLUMN_NAME, ''ColumnID'') > @fieldIndex
-- If the column in scope has been modified, insert a record into the Audit table.
select @bit = (@fieldIndex - 1)% 8 + 1
select @bit = POWER(2, @bit - 1)
select @char = ((@fieldIndex - 1) / 8) + 1
if substring(columns_updated(), @char, 1) & @bit > 0 or @Type IN (''I'', ''D'')
begin
-- Get the name of the field whose ColumnID equals the current fieldIndex.
select @fieldName = ''['' + COLUMN_NAME + '']'' from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = @table and columnproperty(object_id(@table), COLUMN_NAME, ''ColumnID'') = @fieldIndex '
-- Select statements have a length limitation. End the statement, then add the rest.
select @tr = @tr + '
set @sql = ''insert into Audit (Type, TableName, PKFields, PKValues, FieldName, OldValue, NewValue, UpdateDate, UserName) select '''''' + @type + '''''', '''''' + @table + '''''', '''''' + @pkFields + '''''', '''''' + @pkValues + '''''', '''''' + @fieldName + '''''', convert(nvarchar(max), d.'' + @fieldName + ''), convert(nvarchar(max), i.'' + @fieldName + ''), '''''' + @updateDate + '''''', '''''' + @user + '''''' from #ins i full outer join #del d'' + @pkJoin + '' where i.'' + @fieldName + '' <> d.'' + @fieldName + '' or (i.'' + @fieldName + '' is null and d.'' + @fieldName + '' is not null) or (i.'' + @fieldName + '' is not null and d.'' + @fieldName + '' is null)''
--print(@sql)
exec(@sql)
end
end'
---- This is if you want to see the statement that is generated rather than execute it.
--select @tr
-- Execute the trigger statement.
exec(@tr)
-- Iterate the table name.
select @tableName = min(TABLE_NAME) from INFORMATION_SCHEMA.TABLES
where TABLE_NAME > @tableName and
TABLE_TYPE = 'BASE TABLE' and TABLE_NAME <> 'sysdiagrams' and TABLE_NAME <> 'Audit'
---- If you want to specify certain tables, uncomment the next line and add your table names.
--and (TABLE_NAME = 'IGAs' or TABLE_NAME = 'IGABudgets' or TABLE_NAME = 'Resolutions' or TABLE_NAME = 'RTAProjects' or TABLE_NAME = 'RTASubProjects')
end
答案 11 :(得分:-4)
这里要小心,因为触发器在ROW级别触发,而不是SQL STATEMENT级别。因此,如果某人“删除了BIGTABLE”,那么您的触发器将触发该表中的每一行(这特别是关于您想知道执行该操作的SQL语句的事实,因此您需要“图”说出“对于声明影响的每一行”。