Change Data Capture(CDC)是SQL Server 2008的一个新特性,它可以记录SQL Server表的插入、更新和删除等表修改活动。利用该新特性的一个很好的例子就是对某个数据仓库进行定期更新。我们以前需要通过使用数据装载程序(ETL)来更新数据仓库中所有在源系统中更改过的数据。在CDC这个新功能出现之前,我们可能只会选择查询源系统表里最新更新的DATETIME列来找出那些行曾经被改动过。虽然这个方法既简单又有效,但是它不能查找出那些行被物理删除了。另外,我们也无法用这个办法来确定被改动过的行改动的地方,我们只能读取被改动过的行的当前状态。而利用CDC,我们不仅可以轻松完成上述任务,还可以通过它来进行更复杂的对于数据修改历史的查询。
CDC的原理是每次对源表(Source Tables)执行insert、update、delete时,数据库事务日志会记录DML造成的变更数据,然后捕获处理过程将日志中源表的变更数据写入变更捕获表(Change Tables),最后ETL工具使用CDC查询函数将变更数据抽取到数据仓库。相比起在源表建立促发器,CDC对源表事务性能影响小,而且可以获取变更元数据。
--演示如下
create database sampledatabase
go
USE SampleDatabase
GO
create table [Orders]
( OrderId int identity(1,1) primary key,
OrderDate datetime default getdate(),
CustomerId varchar(50) not null
)
go
--在数据库级别启用CDC功能
EXEC sys.sp_cdc_enable_db
执行命令后即启用了数据库的CDC特性。启用CDC特性后系统会自动建立名为CDC的构架和用户,并建立了几个用于CDC的数据表。如果存在了非CDC功能创建的CDC用户、架构的话,则需要先删除该cdc命名的架构,才能开启。
SELECT is_cdc_enabled,CASE WHEN is_cdc_enabled=0 THEN 'CDC功能禁用'ELSE 'CDC功能启用'END 描述
FROM sys.databases WHERE NAME='Sampledatabase'
--在需要做数据捕获的表格上面启用CDC功能
EXEC sys.sp_cdc_enable_table @source_schema='dbo',@source_name='Orders',@supports_net_changes=1,@role_name=null
--这里的supports_net_changes指的是是否支持所谓的净更改,即过滤掉重复的,默认情况下会对表的全部列做捕获。
由于sys.sp_cdc_enable_table 的参数:@captured_column_list = NULL,所以dbo.Department表的所有字段都进行监控了,如果你只关心某些字段,强烈建议在创建捕获的时候设置这个属性
--可以多个表格上启用CDC
源表dbo.Orders成功建立捕获实例后,系统自动建立了变更捕获表,变更捕获表的命名加了后缀_CT。
--关于sp_cdc_enable_table存储过程的具体用法和有关参数的含义,请参考
http://msdn.microsoft.com/en-us/library/bb522475.aspx
--查看哪些表格上面启用了CDC功能
SELECT name ,
is_tracked_by_cdc ,
CASE WHEN is_tracked_by_cdc = 0 THEN 'CDC功能禁用'
ELSE 'CDC功能启用'
END 描述
FROM sys.tables
WHERE sys.tables.schema_id<>schema_id('cdc')
--返回所有表的变更捕获配置信息
EXECUTE sys.sp_cdc_help_change_data_capture;
----------------------
--插入或者更新数据测试CDC功能
INSERT Orders(CustomerID) VALUES('Microsoft');
INSERT Orders(CustomerID) VALUES('Google');
UPDATE Orders SET CustomerID='Yahoo' WHERE OrderID=1
DELETE FROM Orders WHERE OrderID=2
--查询CDC的结果
SELECT * FROM cdc.dbo_Orders_CT
变更捕获表cdc.dbo_Orders_CT中除了我们需要保存源表的列外,还多出了5个以”__$”开头的列,用于记录元数据。
其中
__$operation 识别执行的是何种DML,1=delete,2=insert,3=update(更新前),4=update(更新后)
__$update_mask 用2进制标识受影响的列,对insert和delete来说,影响全部的列,所以值是111(十进制的7);对column_key更新时,值是001(十进制1); 对列1更新,值是10(十进制2),对列2更新时,值是100(十进制4)。
但是微软不检查直接查询这类表,建议使用cdc.fn_cdc_get_all_changes_<捕获实例> 和cdc.fn_cdc_get_net_changes_
--按照时间范围查询CDC结果
declare @start_time DATETIME
set @start_time =getdate()-1
DECLARE @end_time DATETIME
set @end_time=getdate()
DECLARE @from_lsn BINARY(10),@end_lsn BINARY(10)
SELECT @from_lsn=sys.fn_cdc_map_time_to_lsn('smallest greater than or equal',@start_time)
SELECT @end_lsn=sys.fn_cdc_map_time_to_lsn(' largest less than or equal',@end_time)
SELECT * FROM cdc.fn_cdc_get_all_changes_dbo_Orders(@from_lsn,@end_lsn,'all')
--如果需要包含更新操作的旧值,则可以以下的语法
DECLARE @from_lsn BINARY(10),@end_lsn BINARY(10)
DECLARE @start_time DATETIME = getdate()-1
DECLARE @end_time DATETIME =getdate()
SELECT @from_lsn=sys.fn_cdc_map_time_to_lsn('smallest greater than or equal',@start_time)
SELECT @end_lsn=sys.fn_cdc_map_time_to_lsn(' largest less than or equal',@end_time)
SELECT * FROM cdc.fn_cdc_get_all_changes_dbo_Orders(@from_lsn,@end_lsn,'all update old')
关于sys.fn_cdc_map_time_to_lsn这个函数,请参考
http://msdn.microsoft.com/en-us/library/bb500137.aspx
--如查询某个时间段插入的数据:
DECLARE @bglsn VARBINARY(10)=sys.fn_cdc_map_time_to_lsn('smallest greater than or equal',getdate()-1)
DECLARE @edlsn VARBINARY(10)=sys.fn_cdc_map_time_to_lsn('largest less than or equal',GETDATE())
SELECT * FROM cdc.dbo_orders_CT
WHERE [__$operation]=2 and [__$start_lsn] BETWEEN @bglsn AND @edlsn
-- 查询变更时间:
SELECT [__$operation] ,
CASE [__$operation] WHEN 1 THEN '删除' WHEN 2 THEN '插入' WHEN 3 THEN '更新(捕获的列值是执行更新操作前的值)'
WHEN 4 THEN '更新(捕获的列值是执行更新操作后的值)' END [类型],
sys.fn_cdc_map_lsn_to_time([__$start_lsn]) [更改时间] ,
orderid,orderdate,customerid
FROM cdc.dbo_Orders_CT
--DDL变更捕获
ALTER TABLE dbo.Orders ALTER COLUMN CustomerId VARCHAR(100) ;
GO
SELECT * FROM cdc.ddl_history
可以结合SSIS实现事实表的增量更新,比如读取CDC的数据,先进行一些查找,然后按照__$operation的值拆分成为三个操作,分别进行插入,更新和删除,这样就可以实现对事实表的增量更新
在数据库的CDC构架中,除了变更捕获表外,可看到有5个在数据库启用CDC时建立的表:
表名 作用
cdc.captured_columns 记录所有CDC实例要保存的列
cdc.change_tables 记录所有的CDC实例
cdc.ddl_history 记录所有源表由DDL产生的变更
cdc.index_columns 记录CDC实例使用的唯一索引
cdc.lsn_time_mapping 记录日志序列号的时间,每个DML事务都有一个日志序列号
SQL Server 2008里还有多个CDC函数和储存过程,用于查询变更数据。
1. 日志序列号与事务时间的变换
在change tables中没有记录事务发生的时间,只记录了事务的日志序列号(lsn),而日志序号号对应的时间记录在lsn_time_mapping表中。sys.fn_cdc_map_lsn_to_time和sys.fn_cdc_map_time_to_lsn是两个用于转换日志序列号与事务时间的函数;sys.fn_cdc_map_time_to_lsn用于获取某一时间段内的所有日志序列号。
2. 最小和最大日志序列号
sys.fn_cdc_get_min_lsn和 sys.fn_cdc_get_max_lsn函数获得目前存在的最大和最小日志序列号。
3. 查询变更数据
cdc.fn_cdc_get_all_changes_函数用于查询实例中满足要求的所有变更记录。cdc.fn_cdc_get_net_changes_函数用于查询实例中满足要求的净变更记录,所谓的净变更记录既是最后一次DML操作后源表的记录,比如在对一行数据进行了多次update后,使用cdc.fn_cdc_get_all_changes_会返回所有更新前和更新后的数据记录,而净变更只返回最后一次更新后的记录。
4. 获取变更列
在对源表进行update操作后,有时需要知道更新的是哪一列。在变更捕获表中__$update_mask字段保存变更列的2进制编码。sys.fn_cdc_is_bit_set用于返回列序的二进制值,比如要知道第3 列是否变更,使用sys.fn_cdc_is_bit_set( 3, __$update_mask ),若返回1,则表明第3列变更,返回0,则表明没有变更。另外要知道实例中的列是第几列,可使用sys.fn_cdc_get_column_ordinal函数。
5. 获取源表DDL变更历史
sys.sp_cdc_get_ddl_history函数用于查询对源表使用数据定义语句的历史,通常在用DDL改变源表时,也要使用同样的DLL改变变更捕获表。比如删除源表中某一列,或者将某一列的值类型由int改成long,那么变更捕获表也要跟着变化。
变更数据的清理
变更捕获表中数据要周期性的加载到数据仓库中,被加载后的数据就要清理掉,否则用于cdc的数据会越来越多。使用sys.sp_cdc_cleanup_change_table存储过程清除变更数据。此外,在启用数据库CDC时,系统自动在SQL Server Agent中加入每日清除变更数据的作业。
停用CDC
sys.sp_cdc_disable_table_change_data_capture存储过程用于停用CDC实例。sys.sp_cdc_disable_db_change_data_capture存储过程用于停用数据库CDC功能。
--撤销CDC
EXEC sys.sp_cdc_disable_table 'dbo',
'Orders','All'
EXEC sys.sp_cdc_disable_db
注意:
1.这个更改跟踪不会永远保存,它会定期清除的。捕获和清除作业都是使用默认参数创建的。捕获作业。它连续运行,每个扫描周期最多可处理 1000 个事务,并在两个周期之间停顿 5 秒钟。清除作业在每天凌晨 2 点运行一次。它将更改表项保留三天(4320 分钟) 。
2.在为源表启用变更数据捕获后,负责填充更改表的捕获进程将忽略未指定进行捕获的任何新列。如果删除了某个跟踪的列,则会为在后续更改项中为该列提供 Null 值。但是,如果现有列更改了其数据类型,则会将更改传播到更改表,捕获进程还会将检测的跟踪表列结构的任何更改发送到 cdc.ddl_history 表。
3.如果某部分日志,CDC的进程还没有读取,那么在截断日志时就会忽略这个部分。捕获进程是一个独立的,它随着代理服务启动而启动。两次扫描之间间隔5分钟。
3.CDC功能依赖Agent服务,因为它有两个操作都是通过作业来启动的。
-----------------------
CREATE TABLE [dbo].[Department](
[DepartmentID] [smallint] not null,
[Name] [nvarchar](200) not NULL,
[GroupName] [nvarchar](50) NOT NULL,
[ModifiedDate] [datetime] NOT NULL,
[AddName] [nvarchar](120) NULL
)
GO
EXEC sys.sp_cdc_enable_table
@source_schema= 'dbo',@source_name = 'Department',
@supports_net_changes = 0,
@index_name = NULL,
@role_name=null,
@captured_column_list = NULL
SELECT name, is_tracked_by_cdc ,
CASE WHEN is_tracked_by_cdc = 0 THEN 'CDC功能禁用' ELSE 'CDC功能启用' END 描述
FROM sys.tables
WHERE OBJECT_ID= OBJECT_ID('dbo.Department')
/******* Step4:测试DML变更捕获*******/
--测试插入数据
INSERT INTO dbo.Department(
DepartmentID,
Name ,
GroupName ,
ModifiedDate
)VALUES(101,'Marketing','Sales and Marketing',GETDATE())
go
SELECT * FROM cdc.dbo_Department_CT
--测试更新数据
UPDATE dbo.Department SET Name = 'Marketing Group',ModifiedDate = GETDATE()
WHERE Name = 'Marketing'
SELECT * FROM cdc.dbo_Department_CT
--测试删除数据
DELETE FROM dbo.Department WHERE Name='Marketing Group'
SELECT * FROM cdc.dbo_Department_CT
go
INSERT INTO dbo.Department(
DepartmentID,
Name ,
GroupName ,
ModifiedDate
)VALUES(102,'Technical','Engineers',GETDATE())
go
SELECT * FROM cdc.dbo_Department_CT
DECLARE @from_lsn binary(10), @to_lsn binary(10)
SET @from_lsn =
sys.fn_cdc_get_min_lsn('dbo_Department')
SET @to_lsn = sys.fn_cdc_get_max_lsn()
SELECT * FROM cdc.fn_cdc_get_all_changes_dbo_Department
(@from_lsn, @to_lsn, N'all update old');
GO
/******* 查看某时间段所有CDC记录*******/
DECLARE @bglsn VARBINARY(10)=sys.fn_cdc_map_time_to_lsn('smallest greater than or equal',getdate()-1)
DECLARE @edlsn VARBINARY(10)=sys.fn_cdc_map_time_to_lsn('largest less than or equal',GETDATE())
SELECT * FROM cdc.dbo_Department_CT
WHERE [__$start_lsn] BETWEEN @bglsn AND @edlsn
DECLARE @from_lsn BINARY(10),@end_lsn BINARY(10)
DECLARE @start_time DATETIME = '2014-02-17 14:50:17.373'
DECLARE @end_time DATETIME =getdate()
SELECT @from_lsn=sys.fn_cdc_map_time_to_lsn('smallest greater than or equal',@start_time)
SELECT @end_lsn=sys.fn_cdc_map_time_to_lsn(' largest less than or equal',@end_time)
SELECT * FROM cdc.[fn_cdc_get_all_changes_dbo_Department] (@from_lsn,@end_lsn,'all update old')
/******* 查看某时间段所有CDC记录*******/
/*
all 其中的update,只包含新值
all update old 包含新值和旧值
*/
-- all 其中的update,只包含新值
-- all update old 包含新值和旧值
******* Step5:维护CDC *******/
--返回所有表的变更捕获配置信息
EXECUTE sys.sp_cdc_help_change_data_capture;
--返回某个表的变更捕获配置信息
EXEC sys.sp_cdc_help_change_data_capture 'dbo', 'Department'
--查看对某个表的哪些列做了捕获监控,使用上面返回的capture_instance列值
EXEC sys.sp_cdc_get_captured_columns
@capture_instance = 'dbo_Department'