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的值拆分成为三个操作,分别进行插入,更新和删除,这样就可以实现对事实表的增量更新
本文所有的代码如下
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'