Pau*_*ams 2 sql-server concurrency sql-server-2008-r2 timestamp
我看到时间戳(rowversion) 列出现了一些意外行为。我创建了一个测试表:
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
Run Code Online (Sandbox Code Playgroud)
我启动了两个线程同时运行插入到这个表中。第一个线程正在运行以下代码:
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
Run Code Online (Sandbox Code Playgroud)
第二个线程正在运行相同的代码,只是它正在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 ;
Run Code Online (Sandbox Code Playgroud)
这张桌子上有一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
Run Code Online (Sandbox Code Playgroud)
我的问题是:
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
Run Code Online (Sandbox Code Playgroud)
IDENTITY 生成器没有很好的文档记录。但是,可以观察到一些似乎相关的行为:
身份生成不会受到交易的影响。这意味着一旦一个值被使用,它就不会被重用,即使导致它使用的事务被回滚。
并非每次使用都会导致更新被写回数据库的序列位置。例如,您可以在崩溃后看到这一点。崩溃后的下一个使用值通常比前一个高几个数字。
虽然没有证据(意思是文档),但可以假设出于性能原因,多行插入会获取标识值块并使用它们直到用完为止。另一个并发线程将获取下一个数字块。此时,标识值实际上不再反映插入的顺序。
另一方面,rowversion 数据类型是一个不断增加的数字,它将反映插入顺序。(时间戳是 rowversion 已弃用的同义词。)
因此,在您的情况下,您可以假设行是按照 rowversion 列的顺序插入的,并且无序标识值是由内存性能优化引起的。
顺便说一下,虽然 IDENTITY 生成器没有很好的文档记录,但新的 2012SEQUENCE功能是。在这里,您可以按顺序阅读有关上述行为的所有信息。
至于您对复制的关注:
事务复制使用数据库日志,不依赖于特定的列值。
合并复制使用 rowguid 列来标识行。这是一个被赋值一次并且在行的整个生命周期中不会改变的列。合并复制不使用 rowversion 列。事务一致性是通过在同步时使用正常锁定这一事实来强制执行的,因此事务要么对合并代理完全可见,要么完全不可见。
快照复制根本不查找更改。它只需要在同步时提交数据并将其复制过来。
| 归档时间: |
|
| 查看次数: |
5160 次 |
| 最近记录: |