http://www.cnblogs.com/downmoon/archive/2012/04/10/2439462.html
本文主要介绍SQL Server中记录数据变更的四个方法:触发器、Output子句、变更数据捕获(Change Data Capture 即CDC)功能、同步更改跟踪。其中后两个为SQL Server 2008所新增。
一、触发器
在SQL Server的早期版本中,如果要记录某个表或视图的Insert/Update/Delete操作,我们可以借助触发器(Trigger)(http://msdn.microsoft.com/zh-cn/library/ms189799.aspx),这在数据量较小的情况下往往是有效的方式之一,其中后触发器(After Trigger)只能跟踪表的三个操作中的任意组合,而前触发器(Instead Of trigger)可以处理表和视图的更新(即使普通的Update View语句在某些列不明确的情况下报错)。我们看两个例子:
准备基础数据:
USE testDb2 GO --创建两个测试表 IF NOT OBJECT_ID('DepartDemo') IS NULL DROP TABLE [DepartDemo] GO IF NOT OBJECT_ID('DepartChangeLogs') IS NULL DROP TABLE [DepartChangeLogs] GO --测试表 CREATE TABLE [dbo].[DepartDemo]( [DID] [int] IDENTITY(101,1) NOT NULL PRIMARY KEY, [DName] [nvarchar](200) NULL, [DCode] [nvarchar](500) NULL, [Manager] [nvarchar](50) NULL, [ParentID] [int] NOT NULL DEFAULT ((0)), [AddUser] [nvarchar](50) NULL, [AddTime] [datetime] NULL, [ModUser] [nvarchar](50) NULL, [ModTime] [datetime] NULL, [CurState] [smallint] NOT NULL DEFAULT ((0)), [Remark] [nvarchar](500) NULL, [F1] [int] NOT NULL DEFAULT ((0)), [F2] [nvarchar](300) NULL ) GO --记录日志表 CREATE TABLE [DepartChangeLogs] ([LogID] [bigint] IDENTITY(1001,1) NOT NULL PRIMARY KEY, [DID] [int] NOT NULL, [DName] [nvarchar](200) NULL, [DCode] [nvarchar](500) NULL, [Manager] [nvarchar](50) NULL, [ParentID] [int] NOT NULL DEFAULT ((0)), [AddUser] [nvarchar](50) NULL, [AddTime] [datetime] NULL, [ModUser] [nvarchar](50) NULL, [ModTime] [datetime] NULL, [CurState] [smallint] NOT NULL DEFAULT ((0)), [Remark] [nvarchar](500) NULL, [F1] [int] NOT NULL DEFAULT ((0)), [F2] [nvarchar](300) NULL, [LogTime] DateTime Default(Getdate()) Not Null, [InsOrUpd] char not null ) GO
创建触发器:
/******* 创建一个After DML触发器 ******/ /********* [email protected] 邀月***************/ CREATE TRIGGER dbo.tri_LogDepartDemo ON [dbo].[DepartDemo] AFTER INSERT, Delete /************此处使用update与“Insert,Delete”效果是一样的,邀月注 **********/ AS SET NOCOUNT ON --屏蔽触发器发送“受影响的行数”给应用程序 -- Inserted rows INSERT [DepartChangeLogs] (DID,[DName], [DCode], [Manager], [ParentID], [AddUser], [AddTime], [ModUser], [ModTime], [CurState], [Remark], [F1], [F2], LogTime, InsOrUPD) SELECT DISTINCT DID,[DName], [DCode], [Manager], [ParentID], [AddUser], [AddTime], [ModUser], [ModTime], [CurState], [Remark], [F1], [F2], GETDATE(), 'I' FROM inserted i -- Deleted rows INSERT [DepartChangeLogs] (DID,[DName], [DCode], [Manager], [ParentID], [AddUser], [AddTime], [ModUser], [ModTime], [CurState], [Remark], [F1], [F2], LogTime, InsOrUPD) SELECT DISTINCT DID,[DName], [DCode], [Manager], [ParentID], [AddUser], [AddTime], [ModUser], [ModTime], [CurState], [Remark], [F1], [F2], GETDATE(), 'D' FROM deleted d GO INSERT [dbo].[DepartDemo] ([DName], [DCode], [Manager], [ParentID], [AddUser], [AddTime], [ModUser], [ModTime], [CurState], [Remark], [F1], [F2]) VALUES (N'国家统计局房产审计一科', N'0', N'胡不归', 0, N'DeomUser', CAST(0x00009DF7017B6F96 AS DateTime), N'', CAST(0x0000000000000000 AS DateTime), 1, N'专业评估全国房价,为老百姓谋福祉', 0, N'') GO ----该Update不会被触发器记录,但Update会生效 UPDATE departDemo SET [Manager]='任我行' WHERE DID=101 GO DELETE FROM departDemo where DID=101 GO SELECT * FROM [DepartChangeLogs]
统计效果:
如果你觉得触发器过于浪费,你可以试着根据某些字段以缩小触发器的范围
/********* 使用DML触发器记录特定列的修改 ***/ /********* [email protected] 邀月***************/ CREATE TRIGGER dbo.[tri_LogDepartDemo2] ON [dbo].[DepartDemo] AFTER Update AS IF Update([Manager]) Begin print '该部门主管实行终身任免制,不得中途更改!' Rollback ----回滚Update操作 End GO UPDATE departDemo SET [Manager]='任我行' WHERE DID=101 GO
执行结果:
但触发器的缺陷也是显而易见的,使用触发器请注意以下几点:
1、触发器通常很隐蔽,换句话说,易忘记,特别在检查性能和逻辑问题时。
2、长时间运行的触发器会严重减慢数据操作,特别是在数据频繁修改的数据库中。
3、不记录日志的更新不会引起DML触发器的触发(如WRITETEXT、Trunacte table及批量插入操作)。
4、约束通常比触发器运行更快。
5、处理某些逻辑时,存储过程通常比触发器要更易维护和管理。
6、不允许在触发器中使用Select返回结果集。
关于触发器的更多内容,请看MSDN(http://msdn.microsoft.com/zh-cn/library/ms189799.aspx)
二、使用Output子句
官方解释:OutPut子句(http://technet.microsoft.com/zh-cn/library/ms177564.aspx)返回受 INSERT、UPDATE、DELETE 或 MERGE 语句影响的各行中的信息,或返回基于受这些语句影响的各行的表达式。 这些结果可以返回到处理应用程序,以供在确认消息、存档以及其他类似的应用程序要求中使用。 也可以将这些结果插入表或表变量。 另外,您可以捕获嵌入的 INSERT、UPDATE、DELETE 或 MERGE 语句中 OUTPUT 子句的结果,然后将这些结果插入目标表或视图。
举例:
/********* 使用Output记录表记录的修改 *****/ /********* [email protected] 邀月***************/ ----删除前面的触发器 Drop TRIGGER dbo.[tri_LogDepartDemo] DROP TRIGGER dbo.[tri_LogDepartDemo2] INSERT [dbo].[DepartDemo] ([DName], [DCode], [Manager], [ParentID], [AddUser], [AddTime], [ModUser], [ModTime], [CurState], [Remark], [F1], [F2]) OUTPUT Inserted.*,getdate(),'I' ---注意这行是新增的 INTO DepartChangeLogs ---注意这行是新增的 VALUES (N'发改委', N'0', N'向问天', 0, N'DeomUser', CAST(0x00009DF7017B6F96 AS DateTime), N'', CAST(0x0000000000000000 AS DateTime), 1, N'油价,我说了算', 0, N'') GO SELECT * FROM [DepartChangeLogs]
注意:
1、从OUTPUT 中返回的列反映 INSERT、UPDATE 或 DELETE 语句完成之后但在触发器执行之前的数据。
2、SQL Server 并不保证由使用 OUTPUT 子句的 DML 语句处理和返回行的顺序。
3、与触发器相比,OutPut子句可以直接处理Merge语句。
以上两种方法各有千秋,在合适的情况下采取合适的方法才是明智的选择,令人惊喜的是,SQL Server 2008起,为我们提供了更为强大的内建的方法-变更数据捕获(CDC,http://msdn.microsoft.com/zh-cn/library/bb500244%28v=sql.100%29.aspx)和更改跟踪,下面我们隆重介绍它们。
三、使用“变更数据捕获”(CDC)功能
SQL Server 2008提供了内建的方法变更数据捕获(Change Data Capture 即CDC)以实现异步跟踪用户表的数据修改,而且这一功能拥有最小的性能开销。可以用于其他数据源的持续更新,例如将OLTP数据库中的数据变更迁移到数据仓库数据库。
要使用CDC功能,首先我们得在数据库中启用该功能。在此我们沿用上例中使用的数据库Testdb2
/**************异步跟踪数据更新演示*************/ /************* [email protected] 邀月***************/ use master GO IF EXISTS (SELECT [name] FROM sys.databases WHERE name = 'TestDb2') drop DATABASE TestDb2 Go CREATE DATABASE TestDb2 GO --查看是否启用CDC SELECT is_cdc_enabled FROM sys.databases WHERE name = 'TestDb2' USE TestDb2 GO ----启用当前数据库的CDC功能 EXEC sys.sp_cdc_enable_db GO
如果报15517错误,请换用其他owner,邀月注
SELECT is_cdc_enabled FROM sys.databases WHERE name = 'TestDb2' /* is_cdc_enabled 1 */ USE testDb2 GO CREATE TABLE [dbo].[DepartDemo]( [DID] [int] IDENTITY(101,1) NOT NULL PRIMARY KEY, [DName] [nvarchar](200) NULL, [DCode] [nvarchar](500) NULL, [Manager] [nvarchar](50) NULL, [ParentID] [int] NOT NULL DEFAULT ((0)), [AddUser] [nvarchar](50) NULL, [AddTime] [datetime] NULL, [ModUser] [nvarchar](50) NULL, [ModTime] [datetime] NULL, [CurState] [smallint] NOT NULL DEFAULT ((0)), [Remark] [nvarchar](500) NULL, [F1] [int] NOT NULL DEFAULT ((0)), [F2] [nvarchar](300) NULL ) GO /********************************** 需要启用SQL Server Agent服务,否则会报错,邀月注 SQLServerAgent is not currently running so it cannot be notified of this action. ***********************************/ /****** 捕获所有的行变更,只返回行的净变更,其他默认 *******/ EXEC sys.sp_cdc_enable_table @source_schema = 'dbo', @source_name = 'DepartDemo', @role_name = NULL, @capture_instance = NULL, @supports_net_changes = 1, @index_name = NULL, @captured_column_list = NULL, @filegroup_name = default
注意此时,SQL Server 自启动了两个job,一个捕获,一个清除,注意清除是默认凌晨2点,清除72小时以上的数据。如果同一数据库的表中CDC已经启用,不会重建job。
/* Job 'cdc.TestDb2_capture' started successfully. Job 'cdc.TestDb2_cleanup' started successfully. */ --确认表已经被跟踪 SELECT is_tracked_by_cdc FROM sys.tables WHERE name = 'DepartDemo' and schema_id = SCHEMA_ID('dbo') /* is_tracked_by_cdc 1 */ --确认 EXEC sys.sp_cdc_help_change_data_capture 'dbo', 'DepartDemo'
可以看到,SQL Server 增加了一个表[cdc].[dbo_DepartDemo_CT]
相比源表多了个字段:
[__$start_lsn]
,[__$end_lsn]
,[__$seqval]
,[__$operation]
,[__$update_mask]
不建议直接查询该表,而应该使用下面的技巧:
USE TestDb2 GO INSERT [dbo].[DepartDemo] ([DName], [DCode], [Manager], [ParentID], [AddUser], [AddTime], [ModUser], [ModTime], [CurState], [Remark], [F1], [F2]) VALUES (N'银监会', N'0', N'云中鹤', 0, N'DemoUser1', CAST(0x00009DF7017B6F96 AS DateTime), N'', CAST(0x0000000000000000 AS DateTime), 1, N'监管汇率', 0, N'') INSERT [dbo].[DepartDemo] ([DName], [DCode], [Manager], [ParentID], [AddUser], [AddTime], [ModUser], [ModTime], [CurState], [Remark], [F1], [F2]) VALUES (N'统计局', N'0', N'神算子', 0, N'DemoUser2', CAST(0x00009DF7017B6F96 AS DateTime), N'', CAST(0x0000000000000000 AS DateTime), 1, N'统计数据', 0, N'') GO UPDATE [dbo].[DepartDemo] SET Manager='段正淳' WHERE DID =101 DELETE [dbo].[DepartDemo] WHERE DID = 102
要查询变更,我们需要借助大名鼎鼎的日志序列号(Log Sequence Numbers)即LSN(http://msdn.microsoft.com/zh-cn/library/ms190411%28v=sql.100%29.aspx)来实现LSN级别的跟踪数据变更。 下面示例中sys.fn_cdc_map_time_to_lsn(http://msdn.microsoft.com/zh-cn/library/bb500137%28v=sql.100%29.aspx)用于LSN转换为时间。
/******* 使用LSN 查看CDC记录 *********/ --http://msdn.microsoft.com/zh-cn/library/bb500137%28v=sql.100%29.aspx SELECT sys.fn_cdc_map_time_to_lsn ( 'smallest greater than or equal' , '2012-04-09 16:09:30') GO UPDATE [dbo].[DepartDemo] SET Manager='段正淳' WHERE DID =101 DELETE [dbo].[DepartDemo] WHERE DID = 102
要查询变更,我们需要借助大名鼎鼎的日志序列号(Log Sequence Numbers)即LSN(http://msdn.microsoft.com/zh-cn/library/ms190411%28v=sql.100%29.aspx)来实现LSN级别的跟踪数据变更。 下面示例中sys.fn_cdc_map_time_to_lsn(http://msdn.microsoft.com/zh-cn/library/bb500137%28v=sql.100%29.aspx)用于LSN转换为时间。
/******* 使用LSN 查看CDC记录 *********/ --http://msdn.microsoft.com/zh-cn/library/bb500137%28v=sql.100%29.aspx SELECT sys.fn_cdc_map_time_to_lsn ( 'smallest greater than or equal' , '2012-04-09 16:09:30') as BeginLSN /* BeginLSN 0x0000002C000000AA0003 */ SELECT sys.fn_cdc_map_time_to_lsn ( 'largest less than or equal' , '2012-04-09 23:59:59') as EndLSN /* EndLSN 0x0000002C000001C20005 */ /**************查看所有CDC记录*************/ /************* [email protected] 邀月***************/ DECLARE @FromLSN varbinar