处理变更数据 (SQL Server)

变更数据通过表值函数(TVF)提供给变更数据捕获使用者。 这些函数的所有查询都需要两个参数来定义在开发返回的结果集时有资格考虑的日志序列号(LSN)的范围。 绑定间隔的上下 LSN 值都被视为包含在间隔内。

提供了多个函数来帮助确定用于查询 TVF 的相应 LSN 值。 函数 sys.fn_cdc_get_min_lsn 返回与捕获实例有效性间隔关联的最小 LSN。 有效性间隔是当前可用于捕获实例的更改数据的时间间隔。 函数 sys.fn_cdc_get_max_lsn 返回有效期间隔内的最大 LSN。 这些函数 sys.fn_cdc_map_time_to_lsnsys.fn_cdc_map_lsn_to_time 可用于帮助将 LSN 值放置在常规时间线上。 由于变更数据捕获使用封闭的查询间隔,因此有时有必要在序列中生成下一个 LSN 值,以确保更改不会在连续查询窗口中重复。 当需要对 LSN 值的增量调整时,函数 sys.fn_cdc_increment_lsnsys.fn_cdc_decrement_lsn 非常有用。

验证 LSN 边界

建议在使用 TVF 查询之前验证要用于 TVF 查询的 LSN 边界。 空端点或位于捕获实例有效性区间之外的端点将导致更改数据捕获 TVF 返回错误。

例如,当用于定义查询间隔的参数无效或范围不足或行筛选器选项无效时,将针对所有更改的查询返回以下错误。

Msg 313, Level 16, State 3, Line 1

An insufficient number of arguments were supplied for the procedure or function cdc.fn_cdc_get_all_changes_ ...

net changes 查询返回的相应错误如下:

Msg 313, Level 16, State 3, Line 1

An insufficient number of arguments were supplied for the procedure or function cdc.fn_cdc_get_net_changes_ ...

注释

据确认,Msg 313 的消息具有误导性,并且不会传达失败的实际原因。 这种不便的用法源于无法在表值函数(TVF)内部触发显式错误。 然而,与仅仅返回空结果相比,返回一个可识别的错误(即使不准确)被认为是更好的选择。 空结果集无法与返回无更改的有效查询区分开来。

查询所有更改时,如遇授权失败,将返回“授权失败”,如下所示:

Msg 229, Level 14, State 5, Line 1

The SELECT permission was denied on the object 'fn_cdc_get_all_changes_...', database 'MyDB', schema 'cdc'.

当查询净变化时,情况也是如此:

Msg 229, Level 14, State 5, Line 1

The SELECT permission was denied on the object fn_cdc_get_net_changes_...', database 'MyDB', schema 'cdc'.

请参阅模板使用 TRY CATCH 枚举 Net Changes,了解如何截获这些已知的 TVF 错误并返回有关失败的更有意义的信息。

注释

若要在 SQL Server Management Studio 中找到更改数据捕获模板,请在 “视图 ”菜单上单击“ 模板资源管理器”,展开 “SQL Server 模板 ”,然后展开 “更改数据捕获 ”文件夹。

查询功能

根据要跟踪的源表的特征及其捕获实例的配置方式,生成一两个用于查询更改数据的 TVF。

  • 函数 cdc.fn_cdc_get_all_changes_<capture_instance> 返回指定间隔发生的所有更改。 这个函数始终会被生成。 条目始终按照更改的事务提交 LSN 排序,然后按照其事务内的序列值排序返回。 根据选择的行筛选器选项,在更新时返回最后一行(行筛选器选项“all”)或更新时同时返回新值和旧值(行筛选器选项“all update old”)。

  • 当源表启用并且将参数@supports_net_changes 设置为 1 时,会生成函数cdc.fn_cdc_get_net_changes_<capture_instance>

    注释

    仅当源表具有定义的主键或 @index_name 参数用于标识唯一索引时,才支持此选项。

    netchanges 函数为每个修改的源表行返回一个更改。 如果在指定间隔内为行记录了多个更改,则列值将反映行的最终内容。 若要正确识别更新目标环境所需的操作,TVF 必须在时间区间考虑行上的初始操作和行上的最终操作。 当指定行筛选器选项为“all”时,net changes 查询返回的操作将包括插入、删除或更新(新值)。 此选项始终将更新掩码返回为 null,因为存在与计算聚合掩码相关的成本。 如果您需要一个能够反映行的所有更改的聚合掩码,请使用“全部包含掩码”选项。 如果下游处理不需要区分插入和更新,请使用“all with merge”选项。 在这种情况下,操作值将仅接受两个值:1 表示删除,5 表示可以是插入或更新的操作。 此选项消除了确定派生作是插入作还是更新所需的额外处理,并且可以在不需要此差异时提高查询的性能。

从查询函数返回的更新掩码是一种紧凑表示形式,用于标识在一行更改数据中更改的所有列。 通常,捕获列中的一小部分才需要此信息。 函数可用于帮助以应用程序更直接的方式从掩码中提取信息。 函数 sys.fn_cdc_get_column_ordinal 返回给定捕获实例中指定列的序号位置,而函数 sys.fn_cdc_is_bit_set 根据函数调用中传递的序号,返回提供的掩码中的位的奇偶性。 这两个函数一起允许使用更改数据请求有效地提取和返回更新掩码中的信息。 请查看模板“使用掩码枚举全部净更改”以了解这些函数的用法演示。

查询函数场景

以下各节介绍了使用查询函数 cdc.fn_cdc_get_all_changes_< 和 cdc.fn_cdc_get_net_changes_< 查询 capture_instance 的变更数据捕获数据的常见方案。

查询捕获实例有效性间隔内的所有更改

更改数据的最直接请求是在捕获实例的有效性间隔中返回所有当前更改数据。 若要发出此请求,请首先确定有效间隔的上下 LSN 边界。 然后,使用这些值来识别参数 @from_lsn 和 @to_lsn,并将它们传递给查询函数 cdc.fn_cdc_get_all_changes_<capture_instance> 或 cdc.fn_cdc_get_net_changes_<capture_instance>。 使用函数 sys.fn_cdc_get_min_lsn 获取下限, 并sys.fn_cdc_get_max_lsn 获取上限。 请参考模板“有效范围枚举所有更改”,以获取使用查询函数cdc.fn_cdc_get_all_changes_<capture_instance>查询当前所有有效更改的示例代码。 有关使用函数cdc.fn_cdc_get_net_changes_<capture_instance>的类似示例,请参阅“枚举净更改的有效范围”的模板。

查询自上次更改集以来的所有新更改

对于典型应用程序,查询更改数据将是一个持续的过程,对自上次请求以来发生的所有更改发出定期请求。 对于此类查询,可以使用函数 sys.fn_cdc_increment_lsn 从上一查询的上限派生当前查询的下限。 此方法可确保不会重复任何行,因为查询间隔始终被视为封闭的间隔,其中两个终结点都包含在间隔中。 然后,使用函数 sys.fn_cdc_get_max_lsn 获取新请求间隔的高端点。 请参阅模板枚举自上一个请求以来的所有更改的示例代码,以系统地移动查询窗口以获取自上次请求以来的所有更改。

查询到目前为止的所有新更改

在查询函数返回的更改上放置的典型约束是仅包括在上一个请求之间发生的更改,直到当前日期和时间为止。 对于此查询,请将函数sys.fn_cdc_increment_lsn应用于 @from_lsn 上一个请求中使用的值,以确定下限。 由于时间间隔的上限表示为特定时间点,因此必须先将其转换为 LSN 值,然后查询函数才能使用它。 在日期/时间值转换为相应的 LSN 值之前,必须确保捕获过程已处理通过指定上限提交的所有更改。 这是为了确保所有符合条件的更改都已传播到更改表中。 执行此作的一种方法是构建一个等待循环,该循环定期检查是否记录了任何数据库更改表的当前最大提交 lsn 超出了请求间隔的所需结束时间。

延迟循环验证捕获进程是否已处理所有相关日志条目后,请使用函数 sys.fn_cdc_map_time_to_lsn 来确定以 LSN 值表示的新高端点。 为了确保检索通过指定时间提交的所有条目,请调用函数sys.fn_cdc_map_time_to_lsn,并使用选项“最大小于或等于”。

注释

在非活动期间,虚拟条目将添加到表中cdc.lsn_time_mapping,以标记捕获进程已处理到给定提交时间的更改的事实。 这可以防止在没有最近更改进行处理时,捕获过程被认为出现落后的情况。

模板“枚举所有更改至现在”演示了如何使用以前的策略来查询更改数据。

向所有更改结果集添加提交时间

在表 cdc.lsn_time_mapping 中可以找到每个具有关联项的事务在数据库更改表中的提交时间。 通过将请求中返回的 __$start_lsn 值与 cdc.lsn_time_mapping 表项的 start_lsn 值结合在一起,可以返回 tran_end_time 以及更改数据,以在源头进行标记,将更改与事务提交时间进行匹配。 模板《将提交时间追加到所有更改项结果集中》演示了如何执行此联接。

将更改数据与其他来自同一事务的数据联接

有时,将更改数据与在源提交事务时收集的其他信息联接起来很有用。 表cdc.lsn_time_mapping中的tran_begin_lsn列提供了执行此类连接所需的信息。 发生源更新时,必须保存系统动态视图 sys.dm_tran_database_transactions 中database_transaction_begin_lsn的值以及要与更改数据联接的任何其他信息。 使用函数 fn_convertnumericlsntobinary 来比较 database_transaction_begin_lsn 和 tran_begin_lsn 的值。 创建此函数的代码在模板“创建函数”fn_convertnumericlsntobinary中提供。 模板Return All Changes with a Given tran_begin_lsn演示如何实现Join操作。

使用日期时间封装函数进行查询

查询更改数据的典型应用程序方案是使用日期/时间值绑定的滑动窗口定期请求更改数据。 对于此类使用者,变更数据捕获提供了存储过程 sys.sp_cdc_generate_wrapper_function ,该存储过程生成脚本,以便为更改数据捕获查询函数创建自定义包装函数。 这些自定义包装器允许查询间隔表示为日期/时间对。

存储过程的调用选项允许为调用方有权访问的所有捕获实例或仅指定捕获实例生成包装器。 支持的选项还包括指定捕获间隔的高端点是应打开还是关闭,哪些可用捕获列应包含在结果集中,哪些包含的列应具有关联的更新标志。 该过程返回一个结果集,其中包含两列:生成的函数名称,该名称派生自捕获实例名称,以及包装存储过程的 create 语句。 始终生成用于包装所有更改查询的函数。 如果@supports_net_changes参数在创建捕获实例时已设置,那么系统也会生成一个函数,用于封装净变化函数。

应用设计者有责任调用用于生成脚本的存储过程,以生成包装存储过程的创建语句,并执行生成的脚本以创建函数。 创建捕获实例时不会自动发生这种情况。

日期/时间包装器由用户所有,而不是在调用方的默认架构中创建。 生成的函数适合大多数用户无需修改。 但是,在创建函数之前,始终可以向生成的脚本应用进一步的自定义。

用于封装所有更改查询的函数名称是fn_all_changes_加上捕获实例名称。 用于净变化包装器的前缀是 fn_net_changes_。 这两个函数采用三个参数,就像其关联的更改数据捕获表值函数(TVF)一样。 但是,包装器的查询间隔由两个日期/时间值而不是两个 LSN 值绑定。 @row_filter_option这两组函数的参数相同。

生成的包装函数支持以下约定来系统地遍历变更数据捕获时间线:预期 @end_time 上一个间隔的参数用作 @start_time 后续间隔的参数。 包装函数负责将日期/时间值映射到 LSN 值,并确保遵循此约定时不会丢失或重复任何数据。

可以生成包装器以支持在指定查询窗口上封闭的上界或开放的上界。 也就是说,调用方可以指定是否将提交时间等于提取间隔上限的条目包含在间隔内。 默认情况下,包含上限。

如果为 @from_lsn 值或 @to_lsn 值提供了 null 值,则生成的查询 TVF 会失败,但日期/时间包装器函数使用 null 来允许日期/时间包装器返回所有当前更改。 ** 也就是说,如果将 null 作为查询窗口的低端点传递给日期时间封装器,则捕获实例有效性间隔的低端点用于应用在查询的 TVF 中的基础 SELECT 语句中。 同样,如果将 null 作为查询窗口的高端点传递,则从查询 TVF 中进行选择时,将使用捕获实例有效性间隔的高端点。

包装函数返回的结果集包括所有请求的列,后跟一个操作列,被重新编码为一个或两个字符,以标识与该行关联的操作。 如果已请求更新标志,它们会以@update_flag_list 参数中指定的顺序在操作代码之后显示为比特列。 有关自定义生成的日期/时间包装器的调用选项的信息,请参阅sys.sp_cdc_generate_wrapper_function(Transact-SQL)。

模板“实例化包装 TVF With Update Flag”演示如何自定义生成的包装函数,以将指定列的更新标志追加到净更改查询返回的结果集中。 模板"实例化 CDC 包装器 TVF"展示了如何为给定数据库架构中源表创建的所有捕获实例实例化查询 TVF 的 Datetime 包装器。

有关使用日期时间包装器查询变化数据的示例,请参阅模板“使用包装器和更新标志获取净变化”。 这个模板演示了在包装器配置为返回更新标志时,如何使用包装器函数查询净变化。 请注意,基础查询函数需要使用行筛选器选项“所有掩码”,以便在更新时返回非空更新掩码。 为下限和上限日期时间间隔边界传递 Null 值,以指示函数在执行基于 LSN 的查询时,使用有效间隔的低端点和高端点来捕获实例的查询结果。 该查询针对捕获实例的有效范围内发生的源行的每个修改返回一行。

使用 Datetime 封装函数在捕获实例之间的过渡

更改数据捕获最多支持单个跟踪源表的两个捕获实例。 此功能的主要用途是在数据定义语言(DDL)更改为源表时,以适应多个捕获实例之间的转换,扩展可用于跟踪的列集。 转换到新的捕获实例时,保护更高应用程序级别免受基础查询函数名称更改的一种方法是使用包装函数包装基础调用。 然后,确保包装函数的名称保持不变。 切换发生时,可以删除旧的包装器函数,并删除一个具有相同名称的新函数,该名称与引用新查询函数的名称相同。 首先修改生成的脚本以创建同名包装函数,可以切换到新的捕获实例,而不会影响更高的应用程序层。

另请参阅

跟踪数据更改 (SQL Server)
关于变更数据捕获 (SQL Server)
启用和禁用变更数据捕获 (SQL Server)
管理和监视变更数据捕获 (SQL Server)