Thay đổi Data Capture và nhị phân __ $ update_mask


9

Chúng tôi đang sử dụng CDC để nắm bắt các thay đổi được thực hiện cho bảng sản xuất. Các hàng thay đổi đang được xuất ra một kho dữ liệu (informationatica). Tôi biết rằng cột __ $ update_mask lưu trữ những cột nào được cập nhật ở dạng varbinary. Tôi cũng biết rằng tôi có thể sử dụng nhiều hàm CDC khác nhau để tìm ra từ mặt nạ đó những cột đó là gì.

Câu hỏi của tôi là này. Bất cứ ai cũng có thể định nghĩa cho tôi logic đằng sau mặt nạ đó để chúng tôi có thể xác định các cột đã được thay đổi trong kho không? Vì chúng tôi đang xử lý bên ngoài máy chủ, chúng tôi không dễ dàng truy cập vào các chức năng CDC MSSQL đó. Tôi thà chỉ phá vỡ mặt nạ trong mã. Hiệu năng của các chức năng cdc ở đầu SQL là vấn đề đối với giải pháp này.

Nói tóm lại, tôi muốn xác định các cột đã thay đổi bằng tay từ trường __ $ update_mask.

Cập nhật:

Như một sự thay thế gửi một danh sách có thể đọc được của con người về các cột đã thay đổi đến kho cũng có thể chấp nhận được. Chúng tôi thấy điều này có thể được thực hiện với hiệu suất lớn hơn nhiều so với phương pháp ban đầu của chúng tôi.

Câu trả lời CLR cho câu hỏi này dưới đây đáp ứng giải pháp thay thế này và bao gồm các chi tiết về diễn giải mặt nạ cho khách truy cập trong tương lai. Tuy nhiên, câu trả lời được chấp nhận bằng XML PATH là nhanh nhất cho cùng kết quả cuối cùng.


Câu trả lời:


11

Và đạo đức của câu chuyện là ... thử, thử những thứ khác, nghĩ lớn, rồi nhỏ, luôn cho rằng có một cách tốt hơn.

Khoa học thú vị như câu trả lời cuối cùng của tôi là. Tôi quyết định thử một cách tiếp cận khác. Tôi nhớ rằng tôi có thể thực hiện concat với thủ thuật XML PATH (''). Vì tôi biết làm thế nào để lấy thứ tự của mỗi cột thay đổi từ danh sách capt_column từ câu trả lời trước đó, tôi nghĩ rằng sẽ đáng để kiểm tra nếu hàm MS bit hoạt động tốt hơn theo cách chúng tôi cần.

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

Nó sạch hơn (mặc dù không thú vị bằng) tất cả những gì CLR, chỉ trả lại cách tiếp cận về mã SQL gốc. Và, trống cuộn .... trả về kết quả tương tự trong chưa đầy một giây . Vì dữ liệu sản xuất lớn hơn 100 lần mỗi giây.

Tôi đang để lại câu trả lời khác cho mục đích khoa học - nhưng bây giờ, đây là câu trả lời đúng của chúng tôi.


Nối _CT vào tên bảng trong mệnh đề TỪ.
Chris Morley

1
Cảm ơn bạn đã quay lại và trả lời điều này, tôi đang tìm một giải pháp rất giống nhau để chúng tôi có thể lọc nó theo mã sau khi một cuộc gọi SQL đã được thực hiện. Tôi không thích thực hiện một cuộc gọi cho mỗi cột trên mỗi hàng được trả về từ CDC!
nik0lias

2

Vì vậy, sau một số nghiên cứu, chúng tôi quyết định vẫn làm điều này ở phía SQL trước khi chuyển giao cho kho dữ liệu. Nhưng chúng tôi đang thực hiện phương pháp cải tiến này (dựa trên nhu cầu của chúng tôi và sự hiểu biết mới về cách thức hoạt động của mặt nạ).

Chúng tôi nhận được một danh sách các tên cột và vị trí thứ tự của chúng với truy vấn này. Sự trở lại trở lại trong một định dạng XML để chúng ta có thể chuyển sang 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);

Sau đó, chúng tôi chuyển khối XML đó dưới dạng một biến và trường mặt nạ cho hàm CLR trả về chuỗi được phân cách bằng dấu phẩy của các cột đã thay đổi theo trường nhị phân _ $ update_mask. Hàm clr này thẩm vấn trường mặt nạ để thay đổi bit cho từng cột trong danh sách xml và sau đó trả về tên của nó từ thứ tự liên quan.

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

Mã c # clr trông như thế này: (được biên dịch thành một hội đồng gọi là 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;
        }
    }
}

Và chức năng cho CLR như thế này:

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]

Sau đó chúng tôi sẽ thêm danh sách cột này vào hàng và chuyển đến kho dữ liệu để phân tích. Bằng cách sử dụng truy vấn và clr, chúng tôi tránh phải sử dụng hai lệnh gọi hàm trên mỗi hàng cho mỗi thay đổi. Chúng ta có thể bỏ qua quyền thịt với kết quả được tùy chỉnh cho trường hợp chụp thay đổi của chúng ta.

Nhờ bài đăng stackoverflow này được đề xuất bởi Jon Seigel cho cách diễn giải mặt nạ.

Theo kinh nghiệm của chúng tôi với phương pháp này, chúng tôi có thể nhận được danh sách tất cả các cột đã thay đổi từ 10k cdc hàng trong vòng dưới 3 giây.


Cảm ơn vì đã trở lại với một giải pháp, tôi có thể đã sử dụng cho điều đó sớm.
Mark Storey-Smith

Kiểm tra câu trả lời MỚI của tôi trước khi bạn làm. Tuyệt vời như CLR là ... chúng tôi đã tìm thấy một cách thậm chí còn tốt hơn. Chúc may mắn.
RThomas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.