更改数据捕获和__ $ update_mask二进制文件


9

我们正在使用CDC捕获对生产表所做的更改。更改后的行将被导出到数据仓库(informatica)。我知道__ $ update_mask列存储以varbinary形式更新的列。我也知道我可以使用各种CDC函数从该掩码中找出那些列是什么。

我的问题是这个。谁能为我定义该掩膜后面的逻辑,以便我们识别仓库中已更改的列?由于我们在服务器外部进行处理,因此无法轻松访问那些MSSQL CDC函数。我宁愿自己在代码中分解面具。对于此解决方案,SQL端的cdc函数的性能存在问题。

简而言之,我想通过__ $ update_mask字段手动识别已更改的列。

更新:

作为替代方案,也可以将人类可读的变更列清单发送到仓库。我们发现执行此操作的性能远胜于原始方法。

以下CLR对这个问题的回答符合这种选择,并包括为将来的访客解释口罩的详细信息。但是,对于相同的最终结果,使用XML PATH接受的答案是最快的。


Answers:


11

这个故事的寓意是……测试,尝试其他事情,先大后小,再假设有更好的方法。

和我的最后一个答案一样,科学有趣。我决定尝试另一种方法。我记得我可以使用XML PATH('')技巧来进行连接。由于我知道如何从上一个答案的captured_column列表中获取每个更改的列的序数,因此我认为值得测试一下MS位函数是否可以更好地满足我们的需求。

SELECT __$update_mask ,
        ( SELECT    CC.column_name + ','
          FROM      cdc.captured_columns CC
                    INNER JOIN cdc.change_tables CT ON CC.[object_id] = CT.[object_id]
          WHERE     capture_instance = 'dbo_OurTableName'
                    AND sys.fn_cdc_is_bit_set(CC.column_ordinal,
                                              PD.__$update_mask) = 1
        FOR
          XML PATH('')
        ) AS changedcolumns
FROM    cdc.dbo_MyTableName PD

它比所有CLR都干净(尽管不如它有趣),仅将方法返回到本机SQL代码。并且,滚筒滚动...在不到一秒钟的时间内即可返回相同的结果。由于生产数据每秒钟要大100倍。

我将其他答案留作科学用途-但就目前而言,这是我们的正确答案。


将_CT附加到FROM子句中的表名。
克里斯·莫利

1
感谢您回来并回答这个问题,我正在寻找一种非常相似的解决方案,因此一旦完成SQL调用,我们就可以在代码中相应地对其进行过滤。我不希望对CDC返回的每一行的每一列都打电话!
nik0lias 2016年

2

因此,经过一些研究,我们决定在移交给数据仓库之前仍在SQL端执行此操作。但是,我们正在采用这种经过改进的方法(基于我们的需求以及对口罩工作原理的新理解)。

通过此查询,我们可以获得列名及其顺序位置的列表。返回值以XML格式返回,因此我们可以转而使用SQL CLR。

DECLARE @colListXML varchar(max);

SET @colListXML = (SELECT column_name, column_ordinal
    FROM  cdc.captured_columns 
    INNER JOIN cdc.change_tables 
    ON captured_columns.[object_id] = change_tables.[object_id]
    WHERE capture_instance = 'dbo_OurTableName'
    FOR XML Auto);

然后,我们将该XML块作为变量和mask字段传递给CLR函数,该函数返回每个_ $ update_mask二进制字段已更改的列的逗号分隔字符串。此clr函数查询xml列表中每一列的mask字段以获取更改位,然后从相关序数返回其名称。

SELECT  cdc.udf_clr_ChangedColumns(@colListXML,
        CAST(__$update_mask AS VARCHAR(MAX))) AS changed
    FROM cdc.dbo_OurCaptureTableName
    WHERE NOT __$update_mask IS NULL;

C#clr代码如下所示:( 编译成名为CDCUtilities的程序集)

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public partial class UserDefinedFunctions
{
    [Microsoft.SqlServer.Server.SqlFunction]
    public static SqlString udf_clr_cdcChangedColumns(string columnListXML, string updateMaskString)
    {
        /*  xml of column ordinals shall be formatted as follows:

            <cdc.captured_columns column_name="Column1" column_ordinal="1" />                
            <cdc.captured_columns column_name="Column2" column_ordinal="2" />                

        */

        System.Text.ASCIIEncoding encoding=new System.Text.ASCIIEncoding();
        byte[] updateMask = encoding.GetBytes(updateMaskString);

        string columnList = "";
        System.Xml.XmlDocument colList = new System.Xml.XmlDocument();
        colList.LoadXml("<columns>" + columnListXML + "</columns>"); /* generate xml with root node */

        for (int i = 0; i < colList["columns"].ChildNodes.Count; i++)
        {
            if (columnChanged(updateMask, int.Parse(colList["columns"].ChildNodes[i].Attributes["column_ordinal"].Value)))
            {
                columnList += colList["columns"].ChildNodes[i].Attributes["column_name"].Value + ",";
            }
        }

        if (columnList.LastIndexOf(',') > 0)
        {
            columnList = columnList.Remove(columnList.LastIndexOf(','));   /* get rid of trailing comma */
        }

        return columnList;  /* return the comma seperated list of columns that changed */
    }

    private static bool columnChanged(byte[] updateMask, int colOrdinal)
    {
        unchecked  
        {
            byte relevantByte = updateMask[(updateMask.Length - 1) - ((colOrdinal - 1) / 8)];
            int bitMask = 1 << ((colOrdinal - 1) % 8);  
            var hasChanged = (relevantByte & bitMask) != 0;
            return hasChanged;
        }
    }
}

CLR的功能如下:

CREATE FUNCTION [cdc].[udf_clr_ChangedColumns]
       (@columnListXML [nvarchar](max), @updateMask [nvarchar](max))
RETURNS [nvarchar](max) WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [CDCUtilities].[UserDefinedFunctions].[udf_clr_cdcChangedColumns]

然后,我们将此列列表附加到行集,然后传递到数据仓库进行分析。通过使用查询和clr,我们避免了每次更改每行必须使用两个函数调用的情况。我们可以直接为我们的变更捕获实例定制结果,直接跳到最后。

感谢Jon Seigel建议的stackoverflow帖子,以解释面具的方式。

根据我们使用这种方法的经验,我们能够在3秒内从10k cdc行获得所有已更改列的列表。


感谢您返回解决方案,我可能很快就会使用它。
Mark Storey-Smith

在执行操作之前,请先查看我的答案。就像CLR一样酷...我们找到了更好的方法。祝好运。
RThomas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.