데이터 캡처 및 __ $ update_mask 바이너리 변경


9

CDC를 사용하여 프로덕션 테이블의 변경 사항을 캡처하고 있습니다. 변경된 행이 데이터웨어 하우스 (informatica)로 내보내집니다. __ $ update_mask 열에는 varbinary 형식으로 업데이트 된 열이 저장되어 있습니다. 또한 다양한 CDC 함수 를 사용하여 해당 마스크의 열이 무엇인지 확인할 수 있습니다.

내 질문은 이것입니다. 창고에서 변경 된 열을 식별 할 수 있도록 마스크 뒤에 논리를 정의 할 수 있습니까? 우리는 서버 외부에서 처리하기 때문에 MSSQL CDC 기능에 쉽게 액세스 할 수 없습니다. 오히려 코드에서 마스크를 직접 분해하고 싶습니다. SQL 끝에서 cdc 함수의 성능은이 솔루션에 문제가 있습니다.

요컨대, __ $ update_mask 필드에서 직접 변경된 열을 식별하고 싶습니다.

최신 정보:

대체로 사람이 읽을 수있는 변경된 열 목록을웨어 하우스로 전송하는 것도 가능합니다. 우리는 이것이 원래의 접근법보다 훨씬 뛰어난 성능으로 수행 될 수 있음을 발견했습니다.

아래이 질문에 대한 CLR 답변은이 대안을 충족하며 향후 방문자를위한 마스크 해석에 대한 세부 정보를 포함합니다. 그러나 XML PATH를 사용하여 허용되는 답변은 동일한 최종 결과에 대해 가장 빠르지 만 가장 빠릅니다.


답변:


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 코드로만 접근 방식을 반환합니다. 그리고 drum roll ....도 1 초 이내에 같은 결과를 반환합니다 . 생산 데이터가 초당 100 배 더 크기 때문에.

나는 과학적 목적으로 다른 답변을 남겨두고 있습니다. 그러나 지금은 이것이 우리의 정답입니다.


FROM 절에서 테이블 이름에 _CT를 추가하십시오.
Chris Morley

1
돌아와서 이것에 응답 해 주셔서 감사합니다. SQL 호출이 완료되면 코드 내에서 적절히 필터링 할 수 있도록 매우 유사한 솔루션을 찾고 있습니다. CDC에서 반환 된 모든 행의 모든 ​​열을 호출하는 것을 좋아하지 않습니다!
nik0lias

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 블록을 변수로 전달하고 마스크 필드를 _ $ update_mask 이진 필드마다 변경된 열의 쉼표로 구분 된 문자열을 반환하는 CLR 함수에 전달합니다. 이 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을 사용하면 변경 당 행당 두 개의 함수 호출을 사용하지 않아도됩니다. 우리는 변경 캡처 인스턴스에 맞게 사용자 정의 된 결과를 가지고 고기를 바로 건너 뛸 수 있습니다.

Jon Seigel이 마스크를 해석하는 방식으로 제안한이 스택 오버 플로우 게시물 덕분에 .

이 접근법에 대한 경험에서 우리는 3 초 안에 10k cdc 행에서 변경된 모든 열의 목록을 얻을 수 있습니다.


솔루션으로 돌아와 주셔서 감사합니다. 곧 사용할 것입니다.
Mark Storey-Smith

당신이하기 전에 내 새로운 답변을 확인하십시오 . CLR만큼 시원하게 ... 더 나은 방법을 찾았습니다. 행운을 빕니다.
RThomas
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.