SQL Server 2008的特性功能Change Data Capture(CDC)



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'

你可能感兴趣的:(SQL,Server)