Сбор данных изменений и двоичный файл __ $ update_mask

9

Мы используем CDC для записи изменений, внесенных в рабочую таблицу. Измененные строки экспортируются в хранилище данных (informatica). Я знаю, что в столбце __ $ update_mask хранятся столбцы, которые были обновлены в форме varbinary. Я также знаю, что могу использовать различные функции CDC, чтобы узнать из этой маски, что это были за столбцы.

У меня вопрос такой. Может ли кто-нибудь определить для меня логику этой маски, чтобы мы могли идентифицировать столбцы, которые были изменены на складе? Поскольку мы выполняем обработку вне сервера, у нас нет простого доступа к этим функциям MSSQL CDC. Я бы предпочел просто разбить маску самостоятельно в коде. Производительность функций cdc на стороне SQL является проблематичной для этого решения.

Короче говоря, я хотел бы идентифицировать измененные столбцы вручную из поля __ $ update_mask.

Обновить:

В качестве альтернативы была также приемлема отправка удобочитаемого списка измененных столбцов на склад. Мы обнаружили, что это может быть выполнено с производительностью, намного превышающей наш первоначальный подход.

Ответ CLR на этот вопрос, приведенный ниже, соответствует этой альтернативе и включает в себя детали интерпретации маски для будущих посетителей. Однако принятый ответ с использованием XML PATH является самым быстрым, но для того же конечного результата.

RThomas
источник
2
stackoverflow.com/questions/14607325/… ?
Джон Зигель

Ответы:

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 раз больше, каждая секунда считается.

Я оставляю другой ответ в научных целях - но пока это наш правильный ответ.

RThomas
источник
Добавьте _CT к имени таблицы в предложении FROM.
Крис Морли
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 как переменную и поле маски в функцию 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, мы избегаем необходимости использовать два вызова функции на строку на изменение. Мы можем перейти непосредственно к мясу с результатами, настроенными для нашего экземпляра захвата изменений.

Благодаря этому сообщению stackoverflow о переполнении стека, предложенному Джоном Зигелем за манеру интерпретации маски.

В нашем опыте с этим подходом мы можем получить список всех измененных столбцов из строк 10k CDC менее чем за 3 секунды.

RThomas
источник
Спасибо, что вернулись с решением, я мог бы использовать это в ближайшее время.
Марк Стори-Смит
Проверьте мой новый ответ, прежде чем сделать. Как ни крути CLR ... мы нашли еще лучший способ. Удачи.
RThomas