通过变更数据捕获和散列提高合并性能

Posted

技术标签:

【中文标题】通过变更数据捕获和散列提高合并性能【英文标题】:Improving Merge Performance with Change Data Capture and Hash 【发布时间】:2020-02-03 18:15:21 【问题描述】:

今天我正在尝试调整审计数据库的性能。我有一个合法的理由来跟踪对行的更改,并且我已经使用 SQL Server 2016 中的系统版本表方法实现了一组表。

我的整个过程将“原始”数据从源系统放入初始表中。从这里开始,我有一个 MERGE 过程,它从 RAW 表中获取数据,并将 RAW 表中的每一列与可审计的系统版本化暂存表中存在的内容进行比较,并确定发生了什么变化。然后系统行版本控制会告诉我哪些发生了变化,哪些没有发生变化。

这种方法的问题是我的表很宽。其中一些有 400 列或更多。即使有 450,000 条记录的表也需要 SQL Server 大约 17 分钟才能执行 MERGE 操作。它确实降低了我们解决方案的性能,如果我们能够加快它的速度,它似乎会对事情有很大帮助。目前,我们需要为数百个表执行此操作。

目前 RAW 和 STAGE 表都在 ID 列上建立索引。

我在几个地方读到过,我们可能会考虑使用 CHECKSUM 或 HASHBYTES 函数在 RAW 提取中记录值。 (你会怎么称呼它?GUID?UUID?哈希?)。然后,我们将计算的值与 STAGE 表中存在的值进行比较。但问题是:许多列中通常有相当多的 NULL 值。有人建议我们将所有列类型强制转换为相同 (nvarchar(max))?,并且 NULL 值似乎会导致校验和的整个计算失败。所以我也在我的代码中编写了很多 ISNULL(,'UNKNOWN') 语句。

那么 - 这里有没有更好的方法来提高合并的性能?我认为我可以使用行更新的时间戳列作为单个值来比较而不是校验和,但我不确定这是否会通过法律收集/审查。 Legal 担心行可能会在界面之外被编辑,并且列不会总是被更新。我已经看到开发人员使用连接函数(如下所示)将许多列值组合在一起的方法。这似乎是代码密集型并且计算/转换列的成本也很高。

所以我的问题是:

鉴于实际情况,我可以在这里以任何方式提高 MERGE 性能吗? 我应该使用校验和还是哈希字节,为什么? 哪种 hashbytes 方法在这里最有意义? (我只是根据 ID 匹配权将一个 RAW 行与另一个 STAGE 行进行比较)? 我是否遗漏了一些功能,这些功能可能会使这种比较在阅读中更快或更容易 我已经做好了? SQL Server 中除了 CONCAT 之外没有更好的函数来执行此操作,这似乎很奇怪。

我编写了以下代码来展示我正在考虑的一些想法。还有比我下面写的更好的吗?

DROP TABLE IF EXISTS MyTable;

CREATE TABLE MyTable
    (C1 VARCHAR(10),
     C2 VARCHAR(10),
     C3 VARCHAR(10)
     );

INSERT INTO MyTable
    (C1,C2,C3)
VALUES
    (NULL,NULL,NULL),
    (NULL,NULL,3),
    (NULL,2,3),
    (1,2,3);


SELECT
    HASHBYTES('SHA2_256',
    CONCAT(C1,'-',
           C2,'-',
           C3)) AS HashbytesValueCastWithNoNullCheck,


    HASHBYTES('SHA2_256',
    CONCAT(CAST(C1 as varchar(max)),'-',
           CAST(C2 as varchar(max)),'-',
           CAST(C3 as varchar(max)))) AS HashbytesValueCastWithNoNullCheck,


    HASHBYTES('SHA2_256',
    CONCAT(ISNULL(CAST(C1 as varchar(max)),'UNKNOWN'),'-',
           ISNULL(CAST(C2 as varchar(max)),'UNKNOWN'),'-',
           ISNULL(CAST(C3 as varchar(max)),'UNKNOWN'))) AS HashbytesValueWithCastWithNullCheck,
    CONCAT(ISNULL(CAST(C1 as varchar(max)),'UNKNOWN'),'-',
           ISNULL(CAST(C2 as varchar(max)),'UNKNOWN'),'-',
           ISNULL(CAST(C3 as varchar(max)),'UNKNOWN')) AS StringValue,
    CONCAT(C1,'-',C2,'-',C3) AS ConcatString,
    C1,
    C2,
    C3
FROM
    MyTable;

'''

【问题讨论】:

Hashbytes 是我的做法(请注意,即使 SHA2_256 也有可能发生冲突,因此请确保它通过合法,而不仅仅是功能需求)。有没有使用像 Redgate 的 DataCompare 这样的工具的空间?比较工具可能使用相同的底层函数,但我已经看到它执行得非常快,所以也许它们有额外的算法。此外,您需要在您的术语之间添加一个分隔符,否则 'Jo Nethen' = 'Jon Ethen'。 【参考方案1】:

鉴于实际情况,我可以在这里以任何方式提高 MERGE 性能吗?

您应该进行测试,但是为每一行存储一个哈希,为新行计算哈希,并基于 (key,hash) 进行比较应该比比较每一列便宜。

我应该使用校验和还是哈希字节,为什么?

HASHBYTES 丢失更改的可能性要低得多。粗略地说,使用 CHECKSUM,您最终可能会错过一两次更改,而使用 HASHBYTES,您可能永远不会错过任何更改。请参阅此处的备注:BINARY_CHECKSUM。

我是否遗漏了一些功能,这些功能可能会使我在阅读时更快或更容易地进行比较?

没有。没有比较多列的特殊方法。

还有比我下面写的更好的吗?

您绝对应该替换空值,否则一行 (1,null,'A')(1,'A',null) 将获得相同的哈希值。你应该用不会在任何列中显示为值的东西替换空值和定界。如果你有 Unicode 文本,转换为 varchar 可能会删除一些更改,所以使用 nvarchar 更安全。例如:

HASHBYTES('SHA2_256',
    CONCAT(ISNULL(CAST(C1 as nvarchar(max)),N'~'),N'|',
           ISNULL(CAST(C2 as nvarchar(max)),N'~'),N'|',
           ISNULL(CAST(C3 as nvarchar(max)),N'~'))) AS HashbytesValueWithCastWithNullCheck

SQL Server 中的 JSON 非常快。所以你可以试试这样的模式:

select t.Id, z.RowJSON, hashbytes('SHA2_256', RowJSON) RowHash
from SomeTable t
cross apply (select t.* for json path) z(RowJSON)

【讨论】:

作为一个想法,您如何看待结合binary_checksum()hashbytes() 以避免isnull() 的东西? POC 为select *, HASHBYTES('SHA2_256', concat(checksum(a, b, c), a, b, c)) from (values (1, 'A', null), (1, null, 'A')) as x(a, b, c); 这很聪明,但我看不出有办法估计碰撞的概率。请参阅答案更新以获取替代方案。

以上是关于通过变更数据捕获和散列提高合并性能的主要内容,如果未能解决你的问题,请参考以下文章

明文密码和散列值抓取防范方法

散列和散列码

如果数据库是可访问的,那么盐和散列的意义何在?

在 Python 中加盐和散列密码

带有休眠和散列密码的 Spring Security DB 身份验证?

Perl基础---引用3: 数组的散列