【问题标题】:CHECKSUM() collisions in SQL Server 2005SQL Server 2005 中的 CHECKSUM() 冲突
【发布时间】:2009-06-22 19:42:26
【问题描述】:

我有一个包含 5,651,744 行的表,主键由 6 列组成(int x 3、smallint、varchar(39)、varchar(2))。我希望通过此表和另一个共享此主键的表以及添加的附加列但有 37m 行来提高性能。

为了添加一列来创建哈希键,我进行了分析,发现了 18,733 个冲突。

SELECT  SUM(CT)
FROM    (
         SELECT HASH_KEY
               ,COUNT(*) AS CT
         FROM   (
                 SELECT CHECKSUM(DATA_DT_ID, BANK_NUM, COST_CTR_NUM,
                                 GL_ACCT_NUM, ACCT_NUM, APPN_CD) AS HASH_KEY
                 FROM   CUST_ACCT_PRFTBLT
                ) AS X
         GROUP BY HASH_KEY
         HAVING COUNT(*) > 1
        ) AS Y

SELECT  COUNT(*)
FROM    CUST_ACCT_PRFTBLT

BINARY_CHECKSUM() 的情况大约是后者的两倍

考虑到我覆盖的目标空间相对较小,这是否看起来太高(0.33%)?如果冲突如此之高,考虑到您仍然必须加入常规列以处理偶尔的冲突,那么首先在连接中加入这个制造的密钥是否有好处,因为每行额外 4 个字节的成本?

【问题讨论】:

  • 你一次要加入多少条记录?明细表是否有聚集索引?有多宽?如果聚集索引很宽(即它包含所有 FK),您可以将其删除或替换为标识列吗?
  • 为什么这对您来说是个问题?你需要完成什么?
  • 问题是我有 200m 行派生统计数据要从 37m 行统计数据中产生,并且 PIVOT 进行计算必须以一个非常大的键为中心,这导致了令人讨厌的急切线轴所有 37m 行到 tempdb。

标签: sql sql-server-2005 checksum hash-collision


【解决方案1】:

我看不出添加校验和会在哪里获得具有这种级别的冲突的任何东西。即使 1 次冲突也太多了,因为它会导致您加入错误的数据。如果您不能保证加入正确的记录,那么如果它提高了性能但会破坏数据完整性,那将毫无意义。这似乎是财务数据,因此您最好确保您的查询不会返回错误的结果。如果发生任何冲突,您实际上最终可能会借记或贷记错误的帐户。

如果您确实走这条路,Marc 是正确的,您应该尽可能进行预计算(根据我的经验,添加必须对数百万记录表中的每条记录进行的计算不太可能提高性能)。可能如果您可以执行预先计算的列(并且您需要触发器以使其保持最新),那么您可能不需要加入所有其他六个列以确保没有冲突。那么您可能会提高性能。你所能做的就是检验你的理论。但请确保没有任何碰撞。

您是否考虑过使用代理键,然后在六个自然键字段上使用唯一索引?然后你可以加入代理键,这可能会提高性能。加入六列(一列是 varchar)而不是一个代理键是不高效的。我从数据的大小意识到,这可能比在非生产系统中更难重构,但确实值得花时间来永久修复持久的性能问题。只有您可以说这将是多么复杂的更改以及将所有 sps 或查询更改为更好的连接将是多么困难。但是,尝试一下可能是可行的。

【讨论】:

  • 我也必须加入替代品和所有 PK 专栏。代理项必须是索引中的第一列(优化器希望选择的列),但必须连接所有列。此 MSDN 文档中有一个示例(只是搜索,而不是连接):msdn.microsoft.com/en-us/library/ms189788(SQL.90).aspx
  • 为什么需要加入代理键和自然主键列?代理键需要添加到两个表中,但您将使用它而不是您当前在连接中使用的 6 个字段。
  • 我明白了,一个真正的唯一代理,而不仅仅是一个哈希。好吧,不幸的是,我正在重新设计的遗留系统没有 RI,所以实际上 37m 行 stat 表中有条目,而 5m 行 PK 表中没有条目。我得考虑一下。
  • 我最终将真正的唯一代理解决方案作为外键放入类似维度的表中。
【解决方案2】:

到目前为止,我看到很多人都在掩饰CHECKSUM 有大量的冲突,Microsoft's own admission。它甚至比 MD5 还要糟糕,后者有相当多的有意义的冲突。

如果您要获取哈希列,请考虑使用 HASHBYTES 并指定 SHA1。 与MD5CHECKSUM 相比,SHA1 的有意义冲突要少得多。因此,CHECKSUM 绝不应该用于确定行是否唯一,而是用于快速检查两个值的保真度。因此,您与HASHBYTES 的冲突率应为 0%,除非您有重复的行(作为 PK,这绝不应该发生)。

请记住,HASHBYTES 会截断大于 8000 字节的任何内容,但您的 PK 远小于该值(全部连接),因此您应该不会有任何问题。

【讨论】:

  • 我已经重构了架构以在维度表中使用真正的唯一代理,并将其作为三个表的主键。性能大大提高。
【解决方案3】:

如果您的校验和将其降至数据的 0.33%,那么我认为它工作正常……尤其是如果您将此列与其他(索引)列结合使用。

当然,要作为有效的索引,您可能希望在插入/更新数据时使用非聚集索引计算和存储此值。

当然,对相关列的常规跨越索引可能会做得同样好或更好......

【讨论】:

  • 是的,我正计划使用持久计算列。
【解决方案4】:

如果您的查询是选择性的并且行表聚集索引很窄或不存在,那么行表中校验和的非聚集索引应该提供良好的性能。

在对头表应用任何标准后,它将使用校验和对非聚集索引执行索引查找。您仍然需要在连接中包含 FK,但非校验和连接条件将应用于索引后查找、书签后查找。效率很高。

您想针对索引查找进行优化。校验和已经具有高度选择性。添加 FK 会增加索引大小和相应的 I/O,除非它包含足够的其他字段以完全避免书签查找,否则不会有帮助。

由于非聚集索引将包含聚集键或堆指针,您需要 a) 小的聚集键(例如,一个 int 标识列--4 字节指针)或 b) 根本没有聚集索引(8字节指针)。

如果您的查询不是选择性的,或者如果行表聚集索引很大(整个表减去几列),那么我不知道校验和是否会有所帮助(也许更快的索引导航?)。在任何情况下,您都希望将其设为聚集索引或覆盖索引,并且如果头表未首先在校验和上聚集,则会进行很多排序。

如果您能负担得起存储和索引成本,那么一些覆盖索引(标头和详细信息)可能是可行的方法。

【讨论】:

    【解决方案5】:

    如果您的PRIMARY KEY 是集群的,那么您创建的每个索引都将包含此PRIMARY KEY

    加入散列值将使用以下步骤:

    1. 在索引键中定位散列值
      • 在索引数据中定位PRIMARY KEY
      • 使用Clustered Index Seek 定位表格中的PRIMARY KEY

    加入PRIMARY KEY 将仅使用步骤3

    但是,SQL Server 足够聪明,可以考虑到这一点,如果你会像这样加入:

    SELECT  *
    FROM    main_table mt
    JOIN    CUST_ACCT_PRFTBLT cap
    ON      cap.HASH_KEY = mt.HASH_KEY
            AND cap.DATA_DT_ID = mt.DATA_DT_ID
            AND …
    WHERE   mt.some_col = @filter_value
    

    ,它只是不会使用HASH_KEY 上的索引,而是使用单个Clustered Index SeekFilter 来确保哈希值匹配(并且它们总是会匹配)。

    总结:加入PRIMARY KEY即可。

    使用二级索引,你首先需要做一个无用的HASH_KEY搜索,然后仍然需要加入PRIMARY KEY

    【讨论】:

    • 是的,在重新设计过程中,我避免了对这个过程进行过大的重组,但是因为 PK 太宽(并且是聚集的),我想我可能会提取它并使用代理。在这种情况下,哈希是无关紧要的。我的主要问题是 CUST_ACCT_STAT 中确实存在由于原始系统中的错误 RI 而在 CUST_ACCT_PRFTBLT 中没有匹配 PK 的行,因此我还需要推断这些行。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-15
    • 2012-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多