SQL Server 2008中的CDC(Change Data Capture)功能使用及释疑
SQL Server 2008中的CDC(Change Data Capture)功能使用及释疑
关键词:CDC,数据库审计
原文:http://www.cnblogs.com/chenxizhang/archive/2011/08/10/2133408.html
SQL Server 2008中的CDC(Change Data Capture)功能使用及释疑
CDC(Change Data Capture:变更数据捕获)这个功能是SQL Server 2008企业版的功能,它提供了一种新的机制,对表格数据的更新进行跟踪,在数据仓库的建设过程中,通过这种技术,可以简化从业务数据库导入数据的复杂度。
之前我有过两篇文章介绍,最近因为又在和有关客户介绍这方面的应用。发现之前的例子不是那么完整和清楚,特此再整理一篇出来,给大家参考
一、什么是CDC?
变更数据捕获(Change Data Capture ,简称 CDC)记录 SQL Server 表的插入、更新和删除活动。SQLServer的操作会写日志,这也是CDC捕获数据的来源。
开启cdc的源表在插入、更新和删除活动时会插入数据到日志表中。cdc通过捕获进程将变更数据捕获到变更表中,通过cdc提供的查询函数,我们可以捕获这部分数据。
二、开启CDC
2.1、开启CDC的必要条件
-
sqlserver 2008 以上版本
-
需要开启代理服务(作业)
-
磁盘要有足够的空间,保存日志文件
-
表必须要有主键或者是唯一索引
2.2、开启数据库CDC
1、 在需要开启cdc的数据库上执行脚本如下:
if exists(select 1 from sys.databases where name='db_name' and is_cdc_enabled=0) begin exec sys.sp_cdc_enable_db end
2、查询数据库的cdc开启状态
select is_cdc_enabled from sys.databases where name='db_name'
查询结果为“1”,表示开启成功。
2.3、开启表CDC
*注意:表中必须有主键或者唯一索引
1、添加次要数据文件组及文件
数据库右键“属性” >> “文件组”>> ”添加”
“文件” >> “添加”
2、执行以下脚本,开启表cdc
--CDC是数据库文件组的名称 IF EXISTS(SELECT 1 FROM sys.tables WHERE name='table_name' AND is_tracked_by_cdc = 0) BEGIN EXEC sys.sp_cdc_enable_table @source_schema = 'dbo', -- source_schema @source_name = 'table_name', -- table_name @capture_instance = NULL, -- capture_instance @supports_net_changes = 1, -- supports_net_changes @role_name = NULL, -- role_name @index_name = NULL, -- index_name @captured_column_list = NULL, -- captured_column_list @filegroup_name = 'CDC' -- filegroup_name END
3、查看表cdc开启状态
SELECT is_tracked_by_cdc FROM sys.tables WHERE name='table_name'
查询结果为“1”,表示开启成功。
三、使用CDC
开启cdc后会在数据库中生成以下文件,开启数据库GY_DB,开启表VW_GHZDK
下面我们会对部分表和函数进行说明
系统表:
cdc.change_tables:表开启cdc后会插入一条数据到这张表中,记录表一些基本信息
cdc.captured_columns:开启cdc后的表,会记录它们的字段信息到这张表中
cdc.VW_GHZDK_CT(cdc.表名_CT):记录VW_GHZDK表中所有变更的数据,
字段“__$operation”为“1”代表删除,“2”代表插入,“3”执行更新操作前的值,“4”执行更新操作后的值。字段“__$start_lsn”由于更改是来源于数据库的事务日志,所以这里会保存其事务日志的开始序列号(LSN)
函数:
cdc.fn_cdc_get_all_changes_dbo_VW_GHZDK:针对在指定日志序列号 (LSN) 范围内应用到源表的每项更改均返回一行。如果源行在该间隔内有多项更改,则每项更改都会表示在返回的结果集中
cdc.fn_cdc_get_net_changes_dbo_VW_GHZDK:针对指定 LSN 范围内每个已更改的源行返回一个净更改行。也就是说,如果在 LSN 范围内源行具有多项更改,则该函数将返回反映该行最终内容的单一行
sys.fn_cdc_map_time_to_lsn:为指定的时间返回 cdc.lsn_time_mapping 系统表中 start_lsn 列中的日志序列号 (LSN) 值。可以使用此函数系统地将日期时间范围映射到基于 LSN 的范围,以供变更数据捕获枚举函数 cdc.fn_cdc_get_all_changes_<capture_instance> 和 cdc.fn_cdc_get_net_changes_<capture_instance> 返回此范围内的数据更改。
四、最佳实践:案例演示
-----------------------------------
1. 准备一个数据库,里面准备一个表,Orders
2. 启用数据库级别的CDC选项
--在数据库级别启用CDC功能
EXEC sys.sp_cdc_enable_db
这个命令执行完之后,会在系统表里面添加6个表格
3.在需要做数据捕获的表上面启用CDC选项
EXEC sys.sp_cdc_enable_table @source_schema='dbo', @source_name='Orders', @capture_instance='Orders', @supports_net_changes=0, @role_name=null
【备注】关于这个存储过程的具体用法和有关参数的含义,请参考
http://msdn.microsoft.com/en-us/library/bb522475.aspx
执行之后,会有如下的输出消息
这个提示的意思是说,要启动SQL Server Agent。因为CDC功能是要通过一个两个作业来自动化完成的
与此同时,执行上面的命令还将在系统表中添加一个表格
还会添加一个函数
4.插入或者更新数据测试CDC功能
--插入或者更新数据测试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.Orders_CT
我们可以来解释一下上面结果的含义
__$operation=2的情况,表示新增
__$operation=3或者4,表示更新,3表示旧值,4表示新值
__$operation=1的情况,表示删除
很好理解,不是吗?
但是,我们一般都是需要按照时间范围进行检索,对吧,所以,需要使用下面的语法进行查询
--按照时间范围查询CDC结果 DECLARE @from_lsn BINARY(10),@end_lsn BINARY(10) DECLARE @start_time DATETIME = '2011-8-10 00:00:00' DECLARE @end_time DATETIME ='2011-8-11 00:00:00' 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_Orders(@from_lsn,@end_lsn,'all')
关于sys.fn_cdc_map_time_to_lsn这个函数,请参考
http://msdn.microsoft.com/en-us/library/bb500137.aspx
查询的结果如下
如果需要包含更新操作的旧值,则可以以下的语法
DECLARE @from_lsn BINARY(10),@end_lsn BINARY(10) DECLARE @start_time DATETIME = '2011-8-10 00:00:00' DECLARE @end_time DATETIME ='2011-8-11 00:00:00' 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_Orders(@from_lsn,@end_lsn,'all update old')
通常,为了方便起见,我们会将这个查询定义为一个存储过程,如下
--定义存储过程来进行查询 CREATE PROC GetOrdersCDCResult(@start_time DATETIME,@end_time DATETIME) AS BEGIN 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_Orders(@from_lsn,@end_lsn,'all') END
然后,每次需要用的时候,就直接调用即可
--执行存储过程 EXEC GetOrdersCDCResult '2011-8-10','2011-8-11'
5.结合SSIS实现事实表的增量更新
下面展示了一个SSIS 包的设计,这里面读取CDC的数据,先进行一些查找,然后按照__$operation的值拆分成为三个操作,分别进行插入,更新和删除,这样就可以实现对事实表的增量更新
【5】代码汇总
USE SampleDatabase GO --在数据库级别启用CDC功能 EXEC sys.sp_cdc_enable_db --在需要做数据捕获的表格上面启用CDC功能 EXEC sys.sp_cdc_enable_table
@source_schema='dbo',
@source_name='Orders',
@capture_instance='Orders',
@supports_net_changes=0,
@role_name=null --插入或者更新数据测试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.Orders_CT --按照时间范围查询CDC结果 DECLARE @from_lsn BINARY(10),@end_lsn BINARY(10) DECLARE @start_time DATETIME = '2011-8-10 00:00:00' DECLARE @end_time DATETIME ='2011-8-11 00:00:00' 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_Orders(@from_lsn,@end_lsn,'all') --定义存储过程来进行查询 CREATE PROC GetOrdersCDCResult(@start_time DATETIME,@end_time DATETIME) AS BEGIN 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_Orders(@from_lsn,@end_lsn,'all') END --执行存储过程 EXEC GetOrdersCDCResult '2011-8-10','2011-8-11'
【6】CDC的注意事项
【6.1】不能truncate
truncate 了~会提示不能使用的哟~
【6.2】修改表结构的坑
测试数据
EXEC sys.sp_cdc_enable_db CREATE TABLE AAA2 ( ID INT PRIMARY KEY, Col1 NVARCHAR(50), Col2 INT ) EXEC sys.sp_cdc_enable_table @source_schema = 'dbo', @source_name = 'AAA2' INSERT INTO dbo.AAA2( ID, Col1,Col2 ) VALUES ( 1, 'pp',34 ),( 2, 'bb',234 ),( 3, 'cc',12 ) UPDATE dbo.AAA2 SET Col1 = 'dd' WHERE ID = 3 DELETE FROM dbo.AAA2 WHERE ID = 2
SELECT * FROM cdc.dbo_AAA2_CT
__$start_lsn __$end_lsn __$seqval __$operation __$update_mask ID Col1 Col2 ---------------------- ------------- ---------------------- ------------ -------------------- ---- ------ ----------- 0x0000015B0001378F0019 NULL 0x0000015B0001378F0016 2 0x07 1 pp 34 0x0000015B0001378F0019 NULL 0x0000015B0001378F0017 2 0x07 2 bb 234 0x0000015B0001378F0019 NULL 0x0000015B0001378F0018 2 0x07 3 cc 12 0x0000015B000137A50003 NULL 0x0000015B000137A50002 3 0x02 3 cc 12 0x0000015B000137A50003 NULL 0x0000015B000137A50002 4 0x02 3 dd 12 0x0000015B000137AB0005 NULL 0x0000015B000137AB0002 1 0x07 2 bb 234
栗子1 ,删除了一列Col2 然后再插入2条数据,然后还是会存在Col2的列(因为不需要改结构嘛╮(╯_╰)╭),然后跟踪的时候把值设置成空
ALTER TABLE dbo.AAA2 DROP COLUMN Col2 INSERT INTO dbo.AAA2 ( ID, Col1 ) VALUES ( 4, N'DD'),( 5, N'EE') __$start_lsn __$seqval __$operation __$update_mask ID Col1 Col2 0x0000015B000138F40004 0x0000015B000138F40002 2 0x07 4 DD NULL 0x0000015B000138F40004 0x0000015B000138F40003 2 0x07 5 EE NULL
栗子2 ,然后我脑抽的重新把Col2 加进去~然而我改成了字符串类型,然后从新插入数据 ,Col2 没值啊!!!那也正常,因为这是就结构,虽然名字一样,但是ColumnID已经不一样了啊!所以追踪不到是很正常的。
ALTER TABLE dbo.AAA2 ADD Col2 NVARCHAR(50) INSERT INTO dbo.AAA2 ( ID, Col1, Col2 ) VALUES ( 6, -- ID - int N'jj', -- Col1 - nvarchar(50) 'jjj' -- Col2 - int ) __$start_lsn __$seqval __$operation __$update_mask ID Col1 Col2 0x0000015B000138F40004 0x0000015B000138F40002 2 0x07 4 DD NULL 0x0000015B000138F40004 0x0000015B000138F40003 2 0x07 5 EE NULL 0x0000015B000139640003 0x0000015B000139640002 2 0x07 6 jj NULL
栗子3 ,那我更新总可以了吧!当更新的是元结构有的列,是可以更新成功的,但是如果是新列做了改动,则捕获不了,正常嘛(想想既然可以给你配置可跟踪的列,那么不存在这个列表里面的列发生变化不捕捉,就是这个道理罗~)
UPDATE dbo.AAA2 SET Col2 = 'ee' WHERE ID = 4 UPDATE dbo.AAA2 SET Col1 = 'III' WHERE ID = 6 __$start_lsn __$seqval __$operation __$update_mask ID Col1 Col2 0x0000015B000139C00003 0x0000015B000139C00002 4 0x02 6 III NULL
那该如何处理这个问题呢~
从心做一个捕获实例~从心做一个捕获实例~从心做一个捕获实例 重要的事情说3便 ╮(╯_╰)╭。也只有这样罗