sql - 确定性代理键

标签 sql database-design relational-database

我有一个表,其中一个整数标识列作为其他两列(int 和 datetime)的代理键。为了使该键的值在测试和生产环境中保持同步,我想到了创建一个触发器,将代理键设置为某个确定性值,而不是自动生成的身份(在本例中为自然键的哈希值) 。据我所知,这种权衡是引入了冲突的风险(可以通过将代理列更改为 bigint 来抵消)。

CREATE TRIGGER dbo.TRG_TestTable_SetID ON dbo.TestTable 
INSTEAD OF INSERT
AS 
BEGIN

    insert into dbo.TestTable (ID, IntKey, DateKey, MoreData)
    select convert(bigint, hashbytes('md5', convert(binary(4), IntKey) + convert(binary(8), DateKey))), 
        IntKey, DateKey, MoreData
    from inserted
END

从设计的角度来看,这是一个好的解决方案吗?它仍然会比使用自然组合键作为主键表现更好吗?

编辑:自然键中的 int 是另一个表的外键,它是 guid 和 varchar 的代理键。因此,此表上的“自然键”替代方案将是 guid、varchar 和 datetime 的相当难看的组合。

最佳答案

我之前出于类似的原因使用过类似的技术,并且取得了很好的成功。为了获得所需的确定性质量,您可以尝试将复合自然键列值强制转换为字符串,将它们字符串连接在一起,然后从中生成 MD5 哈希值以用作确定性主键。

一些注意事项:

  • 区分大小写。除非您的某些业务键在设计上就区分大小写,否则最好在您的系统中建立一个惯例,首先将字母小写或大写为“对于哈希函数来说,a' 与“A”不同。如果您要从可能手动用户键入的数据创建 key ,这可以帮助避免出现问题。例如,如果用户键入项目编号“itm009876”而不是“ITM009876”,并且您的各种源系统不够强大,无法在存储它们之前确认该值。
  • 字符串强制转换:确保以有意义且非常具体的方式将值强制转换为字符串。例如,使用 ISO 日期和日期时间加时区,或者在强制转换为字符串之前将日期和日期时间转换为 Unix 时间戳整数
  • 字符串分隔符。在连接之前在字符串之间使用良好的字符串分隔符,例如“;”。 (例如,A+CB 的字符串连接不应与 AB+C 相同)
  • 将哈希值存储为二进制:如果可能,将 MD5 哈希值存储为表中的 16 字节二进制值,并使用 HEX() 函数以人类可读的格式显示它。将 MD5 哈希存储为二进制仅使用存储 32 字节十六进制字符串所需空间量的一半,这对于查找和连接的性能具有优势,因为它更短并且完全避免了在特殊字符串比较上浪费的任何可能的周期逻辑。

优点

  • 有时可以避免行数据的意外重复
  • 可以避免不必要的往返于必须生成或检索序列或 UUID 代理 key 的单一授权机构。
  • 单列键更便于最终用户使用。
  • 单列键更便于下游开发人员编写 SQL、生成 URL 等操作。
  • MD5 很古老且很完善,因此大多数 DBMS 都很好地支持它作为 SQL 函数,因此您也可以根据需要在其中使用计算它们,而无需第三方扩展。
  • 使用 MD5 时,冲突极为罕见。就像您的数据中心更有可能被 meteor 摧毁,而不是经历碰撞,即使有数千亿行和一个表。如果您通过 Google 搜索一种使用散列键的流行方法:“数据仓库散列键”,网上就会有很多关于此问题的激烈讨论。

缺点

  • 理论上,MD5 仍然可能发生冲突。许多组织对此仍然非常犹豫。因此,如果您必须在哈希空间上拥有更多字节,并且您可以忍受连接和索引更新期间潜在的性能影响,那么您始终可以选择更长的 SHA 哈希。
  • 世代是复杂的。您必须很好地选择和记录用于生成复合 key 哈希的算法,并与组织中的其他开发人员进行良好的沟通。只要确保每个人都以同样的方式做即可。
  • 由于哈希的非顺序性质,它们在某些情况下查询效率可能很低,例如在聚集索引表中。请小心这一点,因为某些 DBMS 使用聚集索引作为默认值 - 或者甚至可能没有任何其他选项 - 例如 MYSQL 的 InnoDB。堆表通常更好,例如 PostgreSQL 和 Microsoft SQL Server 中支持/默认的堆表。

(抱歉有任何拼写错误和语法错误。我正在手机上写这篇文章。我稍后会尝试回来清理它。)

关于sql - 确定性代理键,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42425675/

相关文章:

php - "Real"使用 Doctrine/MySQL 删除孤儿

mysql - 将 NULL 值替换为连接结果中的值

sql - SQL 'clever enough' 是在一个简单的地方优化掉一个 NULL 检查吗?

database - 当产品差异很大时,设计产品数据库模式的最佳方法是什么?

sql - Postgresql:一列表的主键

database-design - 使用多个模式的优点?

database-design - 数据库设计 : stock & option trades

mysql - 当我们可以使用关键字 ORDER BY 对表进行排序时,为什么要在表中进行 sort_order

mysql - 为总和(工作时间)< x 的用户选择最大日期

mysql - 为什么 MySQL (MariaDB) 使用以下查询计算记录数超过 3 分钟?