jl6*_*jl6 14 sql-server-2008 sql-server constraint sql-server-2014
我有一个应用程序,它在 SQL Server 2008 数据库(非集群)中创建了数百万个表。我想升级到 SQL Server 2014(集群),但在负载下时遇到错误消息:
“数据库中已经有一个名为'PK__tablenameprefix__179E2ED8F259C33B'的对象”
这是系统生成的约束名称。它看起来像一个随机生成的 64 位数字。由于大量表,我是否可能看到冲突?假设我有 1 亿张表,我计算出在添加下一张表时发生碰撞的可能性不到 1 万亿分之一,但这是假设均匀分布的。SQL Server 是否有可能在 2008 和 2014 版本之间更改其名称生成算法以增加冲突的几率?
另一个显着差异是我 2014 年的实例是一个集群对,但我正在努力形成一个假设,为什么会产生上述错误。
PS 是的,我知道创建数百万个表是疯狂的。这是我无法控制的黑盒 3rd 方代码。尽管疯狂,但它在 2008 版中有效,现在在 2014 版中无效。
编辑:仔细检查,生成的后缀似乎总是以 179E2ED8 开头 - 这意味着随机部分实际上只是一个 32 位数字,每次添加新表时,冲突的几率仅为 50 分之一,这与我看到的错误率更接近!
Mar*_*ith 16
SQL Server 能否在系统生成的约束名称中创建冲突?
这取决于约束的类型和 SQL Server 的版本。
CREATE TABLE T1
(
A INT PRIMARY KEY CHECK (A > 0),
B INT DEFAULT -1 REFERENCES T1,
C INT UNIQUE,
CHECK (C > A)
)
SELECT name,
object_id,
CAST(object_id AS binary(4)) as object_id_hex,
CAST(CASE WHEN object_id >= 16000057 THEN object_id -16000057 ELSE object_id +2131483591 END AS BINARY(4)) AS object_id_offset_hex
FROM sys.objects
WHERE parent_object_id = OBJECT_ID('T1')
ORDER BY name;
drop table T1
Run Code Online (Sandbox Code Playgroud)
+--------------------------+-----------+---------------+----------------------+
| name | object_id | object_id_hex | object_id_offset_hex |
+--------------------------+-----------+---------------+----------------------+
| CK__T1__1D498357 | 491357015 | 0x1D498357 | 0x1C555F1E |
| CK__T1__A__1A6D16AC | 443356844 | 0x1A6D16AC | 0x1978F273 |
| DF__T1__B__1B613AE5 | 459356901 | 0x1B613AE5 | 0x1A6D16AC |
| FK__T1__B__1C555F1E | 475356958 | 0x1C555F1E | 0x1B613AE5 |
| PK__T1__3BD019AE15A8618F | 379356616 | 0x169C85C8 | 0x15A8618F |
| UQ__T1__3BD019A91884CE3A | 427356787 | 0x1978F273 | 0x1884CE3A |
+--------------------------+-----------+---------------+----------------------+
Run Code Online (Sandbox Code Playgroud)
+--------------------------+------------+---------------+----------------------+
| name | object_id | object_id_hex | object_id_offset_hex |
+--------------------------+------------+---------------+----------------------+
| CK__T1__59FA5E80 | 1509580416 | 0x59FA5E80 | 0x59063A47 |
| CK__T1__A__571DF1D5 | 1461580245 | 0x571DF1D5 | 0x5629CD9C |
| DF__T1__B__5812160E | 1477580302 | 0x5812160E | 0x571DF1D5 |
| FK__T1__B__59063A47 | 1493580359 | 0x59063A47 | 0x5812160E |
| PK__T1__3BD019AE0A4A6932 | 1429580131 | 0x5535A963 | 0x5441852A |
| UQ__T1__3BD019A981F522E0 | 1445580188 | 0x5629CD9C | 0x5535A963 |
+--------------------------+------------+---------------+----------------------+
Run Code Online (Sandbox Code Playgroud)
对于默认约束、检查约束和外键约束,自动生成名称的最后 4 个字节是约束的 objectid 的十六进制版本。由于objectid保证唯一,名称也必须是唯一的。在 Sybase 中也有这些使用tabname_colname_objectid
对于 Sybase 使用的唯一约束和主键约束
tabname_colname_tabindid,其中 tabindid 是表 ID 和索引 ID 的字符串连接
这也将保证唯一性。
SQL Server 不使用此方案。
在 SQL Server 2008 和 2017 中,它在系统生成的名称末尾使用一个 8 字节的字符串,但算法已更改为如何生成该名称的最后 4 个字节。
在 2008 年,最后 4 个字节表示一个有符号整数计数器,该计数器从object_idby偏移-16000057,任何负值环绕到最大有符号整数。(的意义16000057在于,这是在连续创建之间应用的增量object_id)。这仍然保证了唯一性。
在 2012 年以上,我在约束的 object_id 和通过将名称的最后 8 个字符视为有符号整数的十六进制表示而获得的整数之间根本看不到任何模式。
2017 年调用堆栈中的函数名称表明,它现在创建了一个 GUID 作为名称生成过程的一部分(在 2008 年我没有提到MDConstraintNameGenerator)。我想这是提供一些随机性的来源。显然,它没有使用 GUID 中的全部 16 个字节,但在约束之间变化的 4 个字节中。

我认为新算法是出于某种效率原因而完成的,代价是在极端情况下(例如您的情况)增加了碰撞的可能性。
这是一个非常病态的情况,因为它要求 PK 的表名前缀和列名(因为这会影响最后 8 个字符之前的 8 个字符)在它变得可能之前对于数万个表是相同的,但可以完全复制轻松地与下面。
CREATE OR ALTER PROC #P
AS
SET NOCOUNT ON;
DECLARE @I INT = 0;
WHILE 1 = 1
BEGIN
EXEC ('CREATE TABLE abcdefghijklmnopqrstuvwxyz' + @I + '(C INT PRIMARY KEY)');
SET @I +=1;
END
GO
EXEC #P
Run Code Online (Sandbox Code Playgroud)
在 SQL Server 2017 上针对新创建的数据库运行的示例在一分钟内失败(在创建了 50,931 个表之后)
消息 2714,级别 16,状态 30,第 15 行 数据库中已经有一个名为“PK__abcdefgh__3BD019A8175067CE”的对象。消息 1750,级别 16,状态 1,第 15 行无法创建约束或索引。请参阅以前的错误。
Dav*_*oft 11
假设我有 1 亿张表,我计算出的碰撞几率不到 1 万亿分之一
记住这是“生日问题”。您不是要为单个给定的散列生成冲突,而是要测量多对值中没有一个会发生冲突的概率。
所以对于 N 个表,有 N*(N-1)/2 对,所以这里大约有 10 16对。如果碰撞的概率是 2 -64,单对不碰撞的概率是 1-2 -64,但是有这么多对,这里没有碰撞的概率大约是 (1-2 -64 ) 10 16,或者更像是 1/10,000。参见例如https://preshing.com/20110504/hash-collision-probabilities/
如果它只是一个 32 位散列,则碰撞的概率仅在 77k 值时就超过 1/2。