Matthew Asked: 2016-04-13 19:38:47 +0800 CST2016-04-13 19:38:47 +0800 CST 2016-04-13 19:38:47 +0800 CST SYSUTCDATETIME 是安全的主键吗? 772 我有一个连续修改的已处理事件表。处理时间的 utc 时间和 eventId。 使用 SYSUTCDATETIME() 作为我的主键是否安全?我可以轻松地使用代理键,但感觉很有趣。 sql-server sql-server-2014 1 个回答 Voted Best Answer Solomon Rutzky 2016-04-13T20:10:07+08:002016-04-13T20:10:07+08:00 (请注意问题的原始措辞是:“SYSUTCDATETIME 是安全的聚集索引吗?”) “安全”到底是什么意思?聚簇索引不需要是唯一的,因此它是“安全的”,因为它不会破坏任何东西。但它不能是主键,因为不能保证它是唯一的。只需尝试以下操作,您就会看到从函数返回的值在所有行中都是相同的: SELECT SYSUTCDATETIME(), * FROM sys.objects; 那么问题来了:使用SYSUTCDATETIME()而不是 an到底能得到IDENTITY什么?我假设您正在使用一个DATETIME28 字节的列,但您很可能会INT为该IDENTITY列使用 4 个字节。 因此,我会按原样使用INT填充过孔:IDENTITY 更小(4 个字节而不是 8 个字节;在具有唯一值的理想情况下为 8 个字节,但是当存在重复项时,“uniquifier”值将添加到任何重复的行,即额外的 4 个字节) 保证唯一(不仅对聚簇索引更好,而且有资格成为 PK)。 无论如何它们基本上都是相同的顺序(尽管有时它们可能不会按顺序匹配) 更新 不,我个人不会相信一个值,即使具有如此高分辨率DATETIME2,作为主键,因为两个操作仍然有可能在同一微秒内发生。并且,您不能保证将来任何时候某人或某个进程都不会在单个语句中插入超过 1 条记录。 更新 2 @ypercubeᵀᴹ 在下面的评论中提出了一个很好的观点(我在这里提到的评论很快就会神奇地消失 ;-): 您可以将日期时间列用于聚簇索引,将标识列用于 PK。(这有时很好,如果您的所有或几乎所有查询都依赖于按日期时间排序。因此两个订单偶尔不匹配不会影响性能。) 是的,这有时是一个不错的选择,但由于聚集索引键被复制到非聚集索引中,它并非没有潜在后果: 如果这张表上只有一个非聚集索引,那么无论哪一种索引占用的空间都差不多。但是因为聚簇索引键最终必须是唯一的(至少在内部),因为它们是非聚簇索引的 RowID,对于所有具有重复DATETIME2值的行,将添加额外的 4 字节“uniquifier”。它将被添加两次:一次添加到聚集索引,一次添加到 IDENTITY PK 的非聚集索引。 如果有多个非聚集索引,那么对空间使用的影响将是: (最少 4 个字节 + 任何重复的聚簇索引键值 4 个字节) * number_of_indexes * rows_in_table(如果使用过滤索引则更少) 这些数字对某些人来说可能看起来很小,但如果我们处理数亿行,它们就会加起来。对于那些错误地认为“磁盘很便宜”的人,请考虑 a)企业存储并不便宜,b)索引维护等磁盘操作也不便宜(SSD 的问题不大,但仍然如此)。有关数据建模决策的下游影响的更详细分析,请参阅我在 SQL Server Central 上发表的以下文章:Disk Is Cheap!奥利?(该站点需要免费注册才能查看内容)。 这并不是说“不要做”,而是“只有收益大于成本时才做”。 更新 3 为了完整起见,我应该提到使用DATETIME2值来分离个体INSERT和/或UPDATE语句的一个问题是它们并不像它们看起来那样精细。这是“分辨率”和“精度”之间的问题。的“精度”DATETIME2是小数点后 7 位。但这并不意味着所表示的最细粒度的时间值会在下一个值发生时递增。这就像DATETIME精确到毫秒的值一样,你永远不会得到一个在毫秒位置只有 0、3 或 7 的值: SELECT CONVERT(DATETIME, '2016-04-14 20:30:40.121'), -- 2016-04-14 20:30:40.120 CONVERT(DATETIME, '2016-04-14 20:30:40.122'), -- 2016-04-14 20:30:40.123 CONVERT(DATETIME, '2016-04-14 20:30:40.128'); -- 2016-04-14 20:30:40.127 与值类似DATETIME2,有时调用SYSUTCDATETIME()是完全准确的。但是由于该值不会在下一微秒刷新,因此报告的值将保持不变一段时间,直到下次刷新为止。这就是为什么@Paul 在对问题的评论中发布的简单测试会违反 PK,即使这两个INSERT语句可以说至少相隔 1 微秒。 有关更多详细信息,请参阅以下 Stack Overflow 答案,包括指向 CodeProject.com 上的项目的链接,该项目具有可在 SQLCLR 中使用以克服此限制的代码: SQL Server CLR 集成未按预期调用系统时间
(请注意问题的原始措辞是:“SYSUTCDATETIME 是安全的聚集索引吗?”)
“安全”到底是什么意思?聚簇索引不需要是唯一的,因此它是“安全的”,因为它不会破坏任何东西。但它不能是主键,因为不能保证它是唯一的。只需尝试以下操作,您就会看到从函数返回的值在所有行中都是相同的:
那么问题来了:使用
SYSUTCDATETIME()
而不是 an到底能得到IDENTITY
什么?我假设您正在使用一个DATETIME2
8 字节的列,但您很可能会INT
为该IDENTITY
列使用 4 个字节。因此,我会按原样使用
INT
填充过孔:IDENTITY
更新
不,我个人不会相信一个值,即使具有如此高分辨率
DATETIME2
,作为主键,因为两个操作仍然有可能在同一微秒内发生。并且,您不能保证将来任何时候某人或某个进程都不会在单个语句中插入超过 1 条记录。更新 2
@ypercubeᵀᴹ 在下面的评论中提出了一个很好的观点(我在这里提到的评论很快就会神奇地消失 ;-):
是的,这有时是一个不错的选择,但由于聚集索引键被复制到非聚集索引中,它并非没有潜在后果:
如果这张表上只有一个非聚集索引,那么无论哪一种索引占用的空间都差不多。但是因为聚簇索引键最终必须是唯一的(至少在内部),因为它们是非聚簇索引的 RowID,对于所有具有重复
DATETIME2
值的行,将添加额外的 4 字节“uniquifier”。它将被添加两次:一次添加到聚集索引,一次添加到 IDENTITY PK 的非聚集索引。如果有多个非聚集索引,那么对空间使用的影响将是:
(最少 4 个字节 + 任何重复的聚簇索引键值 4 个字节)
* number_of_indexes
* rows_in_table(如果使用过滤索引则更少)
这些数字对某些人来说可能看起来很小,但如果我们处理数亿行,它们就会加起来。对于那些错误地认为“磁盘很便宜”的人,请考虑 a)企业存储并不便宜,b)索引维护等磁盘操作也不便宜(SSD 的问题不大,但仍然如此)。有关数据建模决策的下游影响的更详细分析,请参阅我在 SQL Server Central 上发表的以下文章:Disk Is Cheap!奥利?(该站点需要免费注册才能查看内容)。
这并不是说“不要做”,而是“只有收益大于成本时才做”。
更新 3
为了完整起见,我应该提到使用
DATETIME2
值来分离个体INSERT
和/或UPDATE
语句的一个问题是它们并不像它们看起来那样精细。这是“分辨率”和“精度”之间的问题。的“精度”DATETIME2
是小数点后 7 位。但这并不意味着所表示的最细粒度的时间值会在下一个值发生时递增。这就像DATETIME
精确到毫秒的值一样,你永远不会得到一个在毫秒位置只有 0、3 或 7 的值:与值类似
DATETIME2
,有时调用SYSUTCDATETIME()
是完全准确的。但是由于该值不会在下一微秒刷新,因此报告的值将保持不变一段时间,直到下次刷新为止。这就是为什么@Paul 在对问题的评论中发布的简单测试会违反 PK,即使这两个INSERT
语句可以说至少相隔 1 微秒。有关更多详细信息,请参阅以下 Stack Overflow 答案,包括指向 CodeProject.com 上的项目的链接,该项目具有可在 SQLCLR 中使用以克服此限制的代码:
SQL Server CLR 集成未按预期调用系统时间