变更数据捕获(Change Data Capture ,简称 CDC)记录 SQL Server 表的插入、更新和删除活动。使用变更数据捕获可以更有效跟踪表对象DML历史操作,对 ETL 等数据转移也非常有用。
变更数据捕获适用版本:
SQL Server 2008 以上的 Enterprise Edition、Developer Edition 和 Evaluation Edition
变更数据捕获原理:
变更数据捕获的更改数据源为 SQL Server 事务日志。当对表启用变更数据捕获时,系统将生成一个与该表结构类似的副本。当对源表进行插入、更新和删除 时,在事务日志会记录相关操作信息。变更数据捕获代理使用异步进程读取事务日志,将相关操作结果应用到副本表(捕获实例表)中,这样就完成了对源表操作的记录跟踪。
变更数据捕获实例:
在数据库 MyDatabase 中创建测试表:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
-- 创建测试表
USE MyDatabase
GO
CREATE
TABLE
CDC_Test
(
id
int
not
null
,
name
varchar
(50)
not
null
,
insertDate datetime
not
null
,
value
numeric
(14,4)
not
null
)
GO
ALTER
TABLE
CDC_Test
ADD
CONSTRAINT
PK_CDC_Test
PRIMARY
KEY
CLUSTERED (id)
GO
CREATE
NONCLUSTERED
INDEX
IX_CDC_Test_NAME
ON
CDC_Test(
name
)
GO
CREATE
UNIQUE
NONCLUSTERED
INDEX
IX_CDC_Test_insertDate
ON
CDC_Test(insertDate)
GO
|
1
2
3
4
5
6
|
-- 查看数据库是否启用cdc
SELECT
name
,is_cdc_enabled
FROM
sys.databases
WHERE
is_cdc_enabled = 1
-- 查看当前数据库表是否启用cdc
SELECT
name
,is_tracked_by_cdc
FROM
sys.tables
WHERE
is_tracked_by_cdc = 1
|
1
2
3
4
5
|
-- 对当前数据库启用cdc
USE MyDatabase
GO
EXECUTE
sys.sp_cdc_enable_db;
GO
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/*
消息 22830,级别 16,状态 1,过程 sp_cdc_enable_db_internal,第 186 行
无法更新元数据来指示已对数据库 MyDatabase 启用了变更数据捕获。执行命令 'SetCDCTracked(Value = 1)' 时失败。
返回的错误为 15404: '无法获取有关 Windows NT 组/用户 'KK\administrator' 的信息,错误代码 0x54b。'。
请使用此操作和错误来确定失败的原因并重新提交请求。
消息 266,级别 16,状态 2,过程 sp_cdc_enable_db_internal,第 0 行
EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配。上一计数 = 0,当前计数 = 1。
消息 266,级别 16,状态 2,过程 sp_cdc_enable_db,第 0 行
EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配。上一计数 = 0,当前计数 = 1。
消息 3998,级别 16,状态 1,第 1 行
在批处理结束时检测到不可提交的事务。该事务将回滚。
*/
-- 原因是数据库所有者为Windows用户,改为“sa”
EXEC
dbo.sp_changedbowner @loginame = N
'sa'
, @map =
false
GO
--依赖别名已删除
|
启动数据库cdc后,接着对指定源表启用 cdc :
1
2
3
4
5
6
7
8
9
|
-- 接着对指定源表启用cdc
EXEC
sys.sp_cdc_enable_table
@source_schema=
'dbo'
,
--源表架构
@source_name =
'CDC_Test'
,
--源表
@role_name =
'CDC_Role'
--角色(将自动创建)
GO
--作业 'cdc.MyDatabase_capture' 已成功启动。
--作业 'cdc.MyDatabase_cleanup' 已成功启动。
|
1
2
3
4
5
|
-- 也可以使用脚本查看跟踪表的信息
EXEC
sys.sp_cdc_help_change_data_capture
GO
EXEC
sys.sp_cdc_help_change_data_capture
'dbo'
,
'CDC_Test'
GO
|
【cdc.captured_columns】
select * from cdc.captured_columns
每个启用变更数据库捕获的表,其已跟踪的列都可在该系统表中查看(参考: cdc.captured_columns )
EXEC sys.sp_cdc_get_captured_columns N'dbo_CDC_Test';
建议使用该存储过程返回列的元数据信息 (参考:sys.sp_cdc_get_captured_columns )
【cdc.change_tables】
select *from cdc.change_tables
每对一个源表启用变更数据捕获时,该表都会记录其实例表的详细信息(参考:cdc.change_tables)
EXEC sys.sp_cdc_help_change_data_capture
EXEC sys.sp_cdc_help_change_data_capture @source_schema= 'dbo' ,@source_name ='CDC_Test'
最多可为每个源表返回两行,为每个捕获实例返回一行 (参考:sys.sp_cdc_help_change_data_capture)
【cdc.index_columns】
select *from cdc.index_columns
对源表中的主键列或者启用变更数据捕获时指定的唯一索引列(指定索引优于主键)记录一行,变更数据捕获使用这些索引列来唯一标识源表中的行。默认情况下,将包括源表的主键列。(参考:cdc.index_columns)
EXEC sys.sp_cdc_help_change_data_capture @source_schema = 'dbo' ,@source_name = 'CDC_Test'
或者使用该存储过程查看(如上图)
【cdc.ddl_history】
select *from cdc.ddl_history
针对启用了变更数据捕获的表所做的每一数据定义语言(DDL)更改返回一行。可以使用此表来确定源表发生DDL 更改的时间以及更改的内容。(参考:cdc.ddl_history)
EXEC sys.sp_cdc_get_ddl_history @capture_instance= 'dbo_CDC_Test'
1
2
3
|
--测试 DDL 操作后再查看
ALTER
TABLE
CDC_Test
ADD
info
VARCHAR
(20)
ALTER
TABLE
CDC_Test
DROP
COLUMN
info
|
【cdc._CT】
select *from cdc.dbo_CDC_Test_CT
捕获实例表:这是最重要的表,该表就是记录源表的所有DML操作记录。每个表对应一个实例表,命名方式为“架构名_表名_CT”
应用于源表的每个插入或删除操作在更改表中各占一行。插入操作生成的行的数据列包含插入后的列值。删除操作生成的行的数据列包含删除前的列值。更新操作需要两行数据:一行用于标识更新前的列值,另一行用于标识更新后的列值。
现在对源表进行插入、更新、删除后,查看该跟踪实例表:
1
2
3
4
5
6
7
8
9
10
|
-- 进行相关操作
insert
into
CDC_Test(id,
name
,insertDate,value)
select
1,
'kk'
,GETDATE(),55
go
update
CDC_Test
set
name
=
'hh'
,value = 50
where
name
=
'kk'
go
delete
from
CDC_Test
where
id = 1
go
select
*
from
cdc.dbo_CDC_Test_CT
|
__$end_lsn : (在 SQL Server 2008中,此列始终为 NULL)
__$seqval :对事务内的行更改顺序
__$operation :源表DML操作
1 = 删除
2 = 插入
3 = 更新(旧值)
4 = 更新(新值)
__$update_mask :基于更改表的列序号的位掩码,用于标识那些发生更改的列
再测试 DDL 对更改表的影响:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
-- 进行相关操作
INSERT
INTO
CDC_Test(id,
name
,insertDate,value)
SELECT
2,
'mm'
,GETDATE(),0
ALTER
TABLE
CDC_Test
ADD
info
VARCHAR
(20)
--添加1新列
INSERT
INTO
CDC_Test(id,
name
,insertDate,value)
SELECT
2,
'mm'
,GETDATE(),0
ALTER
TABLE
CDC_Test
DROP
COLUMN
value
--删除1列
INSERT
INTO
CDC_Test(id,
name
,insertDate)
SELECT
3,
'hh'
,GETDATE()
SELECT
*
FROM
cdc.dbo_CDC_Test_CT
--查看更改表
|
1. 新添加的列(如 info),在更改表中不会添加。但仍可进行跟踪记录操作,只是不在表cdc.captured_columns 中的列则不跟踪记录。
2. 删除了列(如 value),更改表中则标识为null 。即时再添加创建原来的字段,也无效。
增加或者删除一列后,没有记录跟踪,这种情况增么办?
一种方法是:
1. 增删某字段
2. 再对同一个表启用另一个变更数据捕获(新的变更表为源表当前的结构)
3.再根据 ID/时间/唯一键 等从新的跟踪表取数据(非实时获取数据情况,如定期转移数据等)
4. 如觉得每次调用都更改表名,可以使用视图,调用视图查询更改表,视图只要更改对应的表就行。
捕获实例表中的一些约束:
1
2
3
4
5
6
7
8
9
|
Timestamp
/ rowversion 列的数据类型被定义为
binary
(8)
Identity 列的数据类型被定义为
int
或
bigint
对于 LOB 数据类型
varchar
(
max
)、nvarchar(
max
)、varbinary(
max
)、image、text、ntext 和 xml,如果LOB列被更新,则在捕获表才记录更新前的值,否则(即时更新其他列)更新前的值为
null
。这样节省了空间。
Truncate
table
将无法对启用跟踪的表使用
SWITCH PARTITION 部分行将不会被捕获
|
【cdc.lsn_time_mapping】
select * from cdc.lsn_time_mapping
当捕获进程提交每批新的更改数据时,将在该表中为每个具有更改表项的事务添加新的项(参考:cdc.lsn_time_mapping)
建议执行 sys.fn_cdc_map_lsn_to_time和 sys.fn_cdc_map_time_to_lsn系统函数
结合 cdc.fn_cdc_get_all_changes_ 和 cdc.fn_cdc_get_net_changes_ ,可获取一段范围内的数据变化情况。
1
2
3
4
5
6
7
8
9
|
DECLARE
@begin_time datetime, @end_time datetime, @begin_lsn
binary
(10), @end_lsn
binary
(10);
SET
@begin_time =
'2015-05-16 00:00:00.000'
;
SET
@end_time =
'2015-05-17 00:00:00.000'
;
SELECT
@begin_lsn = sys.fn_cdc_map_time_to_lsn(
'smallest greater than or equal'
, @begin_time);
SELECT
@end_lsn = sys.fn_cdc_map_time_to_lsn(
'largest less than'
, @end_time);
--SELECT @begin_lsn,@end_lsn
SELECT
*
FROM
cdc.fn_cdc_get_all_changes_dbo_CDC_Test( @begin_lsn , @end_lsn ,
'all update old'
)
SELECT
*
FROM
cdc.fn_cdc_get_all_changes_dbo_CDC_Test( @begin_lsn , @end_lsn ,
'all'
)
SELECT
*
FROM
cdc.fn_cdc_get_net_changes_dbo_CDC_Test( @begin_lsn , @end_lsn ,
'all'
)
|
些统计信息:参考 管理和监视变更数据捕获 (SQL Server)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
--如果有连续的空结果集(例如,当捕获作业正在连续运行时),则最后一个现有行中的 empty_scan_count 将递增
-- 为变更数据捕获日志扫描会话中遇到的每个错误返回一行
select
*
from
sys.dm_cdc_errors
-- 针对当前数据库中的每个日志扫描会话返回一行。返回的最后一行表示当前会话。
select
*
from
sys.dm_cdc_log_scan_sessions
-- 空扫描的会话
SELECT
*
from
sys.dm_cdc_log_scan_sessions
where
empty_scan_count <> 0
-- 返回最近进行的会话的平均滞后时间
SELECT
latency
FROM
sys.dm_cdc_log_scan_sessions
WHERE
session_id = 0
-- 返回最近会话的平均吞吐量
SELECT
command_count/duration
AS
[Throughput]
FROM
sys.dm_cdc_log_scan_sessions
WHERE
session_id = 0
|
1
2
3
4
5
|
-- 查看捕获作业或清除作业的信息
-- SELECT * FROM msdb.dbo.sysjobs
SELECT
*
FROM
msdb.dbo.cdc_jobs
EXEC
sys.sp_cdc_help_jobs
|
对作业的更改 (参考 sys.sp_cdc_change_job)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
--对作业的更改
EXEC
sys.sp_cdc_change_job
@job_type =
'capture'
,@maxtrans = 1000
--每个扫描循环可以处理的最多事务数
,@maxscans = 10
--为了从日志中提取所有行而要执行的最大扫描循环次数
,@continuous = 1
--连续运行最多处理(max_trans * max_scans) 个事务
,@pollinginterval = 5
EXEC
sys.sp_cdc_change_job
@job_type =
'cleanup'
,@retention = 4320
--更改行将在更改表中保留的分钟数
,@threshold = 5000
--清除时可以使用一条语句删除的删除项的最大数量
--更改后需重启作业
EXEC
sys.sp_cdc_stop_job @job_type = N
'capture'
;
EXEC
sys.sp_cdc_stop_job @job_type = N
'capture'
;
EXEC
sys.sp_cdc_start_job @job_type = N
'cleanup'
;
EXEC
sys.sp_cdc_start_job @job_type = N
'cleanup'
;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
-- 对表禁用变更数据捕获
USE MyDatabase;
GO
EXEC
sys.sp_cdc_disable_table
@source_schema = N
'dbo'
,
@source_name = N
'CDC_Test'
,
@capture_instance = N
'dbo_CDC_Test'
GO
-- 对数据库禁用变更数据捕获
USE MyDatabase;
GO
EXECUTE
sys.sp_cdc_disable_db;
GO
--执行完成后,相关的表、函数、用户、角色、架构、作业都会完全删除!
|