SQL Server 可以在系統生成的約束名稱中創建衝突嗎?
我有一個在 SQL Server 2008 數據庫(非集群)中創建數百萬個表的應用程序。我希望升級到 SQL Server 2014(集群),但在負載下遇到錯誤消息:
“數據庫中已經有一個名為 ‘PK__tablenameprefix__179E2ED8F259C33B’ 的對象”
這是系統生成的約束名稱。它看起來像一個隨機生成的 64 位數字。由於大量表,我是否有可能看到衝突?假設我有 1 億個表,我計算出在添加下一個表時發生碰撞的可能性小於 1 萬億分之一,但這假設是均勻分佈的。SQL Server 是否有可能在 2008 和 2014 版本之間更改其名稱生成算法以增加衝突的機率?
另一個顯著的區別是我的 2014 實例是一個集群對,但我正在努力形成一個假設來解釋為什麼會產生上述錯誤。
PS 是的,我知道創建數百萬張表是瘋狂的。這是我無法控制的黑匣子第 3 方程式碼。儘管很瘋狂,但它在 2008 版中有效,現在在 2014 版中無效。
編輯:仔細觀察,生成的後綴似乎總是以 179E2ED8 開頭——這意味著隨機部分實際上只是一個 32 位數字,每次添加新表時,衝突的機率僅為 50 分之一,這與我看到的錯誤率更接近!
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
2008 年結果範例
+--------------------------+-----------+---------------+----------------------+ | 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 | +--------------------------+-----------+---------------+----------------------+
2017 年結果範例
+--------------------------+------------+---------------+----------------------+ | 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 | +--------------------------+------------+---------------+----------------------+
對於預設約束、檢查約束和外鍵約束,自動生成名稱的最後 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_id
,-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
在 SQL Server 2017 上針對新創建的數據庫執行的範例在一分鐘內失敗(在創建了 50,931 個表之後)
消息 2714,級別 16,狀態 30,第 15 行數據庫中已經有一個名為“PK__abcdefgh__3BD019A8175067CE”的對象。消息 1750,級別 16,狀態 1,第 15 行無法創建約束或索引。請參閱以前的錯誤。