変更データキャプチャと__ $ update_maskバイナリ


9

CDCを使用して、プロダクションテーブルに加えられた変更をキャプチャしています。変更された行は、データウェアハウス(informatica)にエクスポートされます。__ $ update_mask列には、更新された列がvarbinary形式で格納されていることを知っています。また、さまざまなCDC関数を使用して、そのマスクからそれらの列が何であるかを確認できることも知っています。

私の質問はこれです。誰かがそのマスクの背後にあるロジックを定義して、倉庫で変更された列を特定できるようにできますか?サーバーの外で処理しているため、これらのMSSQL CDC関数に簡単にアクセスできません。自分でマスクをコードで分解するだけです。SQL側のcdc関数のパフォーマンスは、このソリューションでは問題があります。

要するに、変更された列を__ $ update_maskフィールドから手動で識別したいと思います。

更新:

別の方法として、変更された列の人間が読めるリストを倉庫に送信することも可能でした。これは、元のアプローチよりもはるかに優れたパフォーマンスで実行できることがわかりました。

以下のこの質問に対するCLRの回答は、この代替案を満たし、将来の訪問者のためのマスクの解釈の詳細が含まれています。ただし、XML PATHを使用した受け入れられた答えは、同じ最終結果に対しては最速です。


回答:


11

そして、物語の教訓は...テストし、他のことを試して、大きく考え、次に小さくし、常により良い方法があると思い込みます。

私の最後の答えと同じくらい科学的に興味深い。他の方法を試すことにしました。XML PATH( '')トリックを使用して連結できることを思い出しました。以前の回答からキャプチャされた列のリストから、変更された各列の序数を取得する方法を知っていたので、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コードのみに戻る方法よりもクリーンです。そして、drum roll ....は同じ結果を1秒未満で返します。生産データは毎秒100倍大きいため、カウントされます。

他の答えは科学的な目的のために残しておきますが、今のところ、これが正しい答えです。


FROM句のテーブル名に_CTを追加します。
Chris Morley

1
戻ってきてこれに答えてくれてありがとう。SQL呼び出しが行われたらコード内でそれに応じてフィルタリングできるように、非常に類似したソリューションを探しています。CDCから返されたすべての行のすべての列を呼び出す必要はありません。
nik0lias 16

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ブロックを変数およびマスクフィールドとしてCLR関数に渡し、_ $ update_maskバイナリフィールドごとに変更された列のコンマ区切り文字列を返します。このclr関数は、xmlリストの各列の変更ビットのマスクフィールドを調べ、関連する序数からその名前を返します。

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を使用することで、変更ごとに行ごとに2つの関数呼び出しを使用する必要がなくなります。変更キャプチャインスタンス用にカスタマイズされた結果で、すぐにスキップできます。

Jon Seigelがマスクを解釈する方法について提案したこのstackoverflow投稿に感謝します。

このアプローチの経験では、3秒以内に、変更されたすべての列のリストを10k cdc行から取得できます。


解決策で戻ってくれてありがとう、私はすぐにそれを使うかもしれません。
Mark Storey-Smith

あなたがする前に私の新しい答えをチェックしてください。CLRと同じくらいクールです...私たちはさらに良い方法を見つけました。幸運を。
RThomas 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.