為什麼時間戳並不總是隨著並發插入而增加?
我看到timestamp (rowversion) columns 的一些意外行為。我創建了一個測試表:
create table Test ( Test_Key int identity(1,1) primary key clustered, Test_Value int, Test_Thread int, ts timestamp ) create nonclustered index IX_Test_Value on Test (Test_Value) -- probably irrelevant
我同時啟動了兩個執行緒執行插入到這個表中。第一個執行緒正在執行以下程式碼:
declare @i int = 0 while @i < 100 begin insert into Test (Test_Value, Test_Thread) select n, 1 from dbo.fn_GenerateNumbers(10000) set @i = @i + 1 end
第二個執行緒正在執行相同的程式碼,只是它是
select n, 2
從函式中插入其執行緒 ID。首先,關於功能。這使用了一系列帶有 ROW_NUMBER() 的交叉連接的公用表表達式,可以非常快速地按順序返回大量數字。我從Itzik Ben-Gan的一篇文章中學到了這個技巧,所以歸功於他。我不認為函式的實現很重要,但無論如何我都會包含它:
CREATE FUNCTION dbo.fn_GenerateNumbers(@count int) RETURNS TABLE WITH SCHEMABINDING AS RETURN WITH Nbrs_4( n ) AS ( SELECT 1 UNION SELECT 0 ), Nbrs_3( n ) AS ( SELECT 1 FROM Nbrs_4 n1 CROSS JOIN Nbrs_4 n2 ), Nbrs_2( n ) AS ( SELECT 1 FROM Nbrs_3 n1 CROSS JOIN Nbrs_3 n2 ), Nbrs_1( n ) AS ( SELECT 1 FROM Nbrs_2 n1 CROSS JOIN Nbrs_2 n2 ), Nbrs_0( n ) AS ( SELECT 1 FROM Nbrs_1 n1 CROSS JOIN Nbrs_1 n2 ), Nbrs ( n ) AS ( SELECT 1 FROM Nbrs_0 n1 CROSS JOIN Nbrs_0 n2 ) SELECT n FROM ( SELECT ROW_NUMBER() OVER (ORDER BY n) FROM Nbrs ) D ( n ) WHERE n <= @count ;
這張桌子上有一個
identity
列。我希望當我通過這個單調遞增的主鍵從表中選擇值時,我也會以相同的順序看到時間戳。時間戳可能不是連續的,因為可能有其他更新,但它們至少是有序的。然而,我看到的卻是不同的。插入按主鍵交錯,但時間戳按執行緒順序排列。
Test_Key Test_Value Test_Thread ts -------- ---------- ----------- ------------------ 20227 227 1 0x000000006EDF3BC5 20228 228 1 0x000000006EDF3BC6 20229 229 1 0x000000006EDF3BC7 20230 230 1 0x000000006EDF3BC8 20231 1 2 0x000000006EDF41E9 -- thread 2 starts with a new ts 20232 2 2 0x000000006EDF41EB 20233 3 2 0x000000006EDF41EC 20234 4 2 0x000000006EDF41ED --<snip lots of thread 2 inserts> 21538 1308 2 0x000000006EDF4710 21539 1309 2 0x000000006EDF4711 21540 1310 2 0x000000006EDF4712 21541 1311 2 0x000000006EDF4713 21542 231 1 0x000000006EDF3BC9 -- This is less than the prior row! 21543 232 1 0x000000006EDF3BCA -- Thread 1 is inserting 21544 233 1 0x000000006EDF3BCB -- from its last ts value 21545 234 1 0x000000006EDF3BCC
我的問題是:
1)為什麼時間戳並不總是隨著並發插入而增加?
如果你能回答這個問題,加分:
2)為什麼並發插入與主鍵重疊而不是一次全部插入? 每個插入都在執行自己的隱式事務,所以我希望主鍵是為了單個執行緒的插入。我沒想到主鍵會交錯。
我對複制知之甚少,無法回答這個問題:
3) 時間戳亂序會導致複製問題嗎? 在上面的例子中,如果執行緒 2 先送出它的數據呢?當執行緒 1 完成時,它的時間戳都低於執行緒 2 插入的記錄。
我查看了正在執行的請求並確認它們沒有並行執行,所以我認為並行性不是問題。
請注意,此查詢在預設 (READ COMMITTED) 隔離級別下執行。如果我將隔離級別提高到 SERIALIZABLE,當執行緒更改時,我仍然會以相反的順序獲得時間戳。
我正在 SQL Server 2008 R2 上對此進行測試。
為了檢查時間戳順序,我正在做一個
select * from Test
,並且我還使用了以下查詢:-- find timestamps out of sequential order select t1.*, t2.* from Test t1 inner join Test t2 on t2.Test_Key = t1.Test_Key + 1 where t2.ts <> t1.ts + 1 -- find timestamps that are less than the prior timestamp select t1.*, t2.* from Test t1 inner join Test t2 on t2.Test_Key = t1.Test_Key + 1 where t2.ts < t1.ts
IDENTITY 生成器沒有很好的文件記錄。但是,可以觀察到一些似乎相關的行為:
- 身份生成不受交易影響。這意味著一旦使用了一個值,它將不會被重用,即使導致其使用的事務被回滾。
- 並非每次使用都會導致序列位置的更新被寫回數據庫。例如,您可以在崩潰後看到這一點。崩潰後的下一個使用值通常比前一個值高幾個數字。
雖然沒有證據(意思是文件),但可以假設出於性能原因,多行插入會抓取一個標識值塊並使用它們直到用完。另一個並發執行緒將獲得下一個數字塊。此時,標識值實際上不再反映插入的順序。
另一方面,rowversion 數據類型是一個不斷增加的數字,它反映了插入順序。(時間戳是 rowversion 的已棄用同義詞。)
因此,在您的情況下,您可以假設行是按 rowversion 列的順序插入的,並且無序標識值是由記憶體性能優化引起的。
順便說一句,雖然 IDENTITY 生成器沒有很好的文件記錄,但 2012 年的新
SEQUENCE
功能是。在這裡,您可以按順序閱讀有關上述行為的所有資訊。至於您對複制的關注:
- 事務複製使用數據庫日誌並且不依賴於特定的列值。
- 合併複製使用 rowguid 列來標識行。這是一個被賦值一次並且在行的整個生命週期中都不會改變的列。合併複製不使用 rowversion 列。事務一致性通過在同步時使用正常鎖定這一事實來強制執行,因此事務對合併代理完全可見或完全不可見。
- 快照複製根本不尋找更改。它只是在同步送出的數據時獲取並複制它。