Sql-Server

為什麼時間戳並不總是隨著並發插入而增加?

  • November 25, 2013

我看到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 生成器沒有很好的文件記錄。但是,可以觀察到一些似乎相關的行為:

  1. 身份生成不受交易影響。這意味著一旦使用了一個值,它將不會被重用,即使導致其使用的事務被回滾。
  2. 並非每次使用都會導致序列位置的更新被寫回數據庫。例如,您可以在崩潰後看到這一點。崩潰後的下一個使用值通常比前一個值高幾個數字。

雖然沒有證據(意思是文件),但可以假設出於性能原因,多行插入會抓取一個標識值塊並使用它們直到用完。另一個並發執行緒將獲得下一個數字塊。此時,標識值實際上不再反映插入的順序。

另一方面,rowversion 數據類型是一個不斷增加的數字,它反映了插入順序。(時間戳是 rowversion 的已棄用同義詞。)

因此,在您的情況下,您可以假設行是按 rowversion 列的順序插入的,並且無序標識值是由記憶體性能優化引起的。

順便說一句,雖然 IDENTITY 生成器沒有很好的文件記錄,但 2012 年的新SEQUENCE功能是。在這裡,您可以按順序閱讀有關上述行為的所有資訊。

至於您對複制的關注:

  1. 事務複製使用數據庫日誌並且不依賴於特定的列值。
  2. 合併複製使用 rowguid 列來標識行。這是一個被賦值一次並且在行的整個生命週期中都不會改變的列。合併複製不使用 rowversion 列。事務一致性通過在同步時使用正常鎖定這一事實來強制執行,因此事務對合併代理完全可見或完全不可見。
  3. 快照複製根本不尋找更改。它只是在同步送出的數據時獲取並複制它。

引用自:https://dba.stackexchange.com/questions/53829