Ian*_*oyd 4 sql-server-2008-r2
我正在尝试解决 SQL Server 2008 R2 的问题,但我正在抓紧稻草。
鉴于数据位于 B 树的叶节点中,并且也存在于索引中,有人可以在单个UPDATE
语句中读取多个值吗?
想象一个进程更新一行:
--Change status from Pending -> Waiting
BEGIN TRANSACTION
UPDATE Transactions SET Status = 'Waiting'
WHERE TransactionID = 12345
COMMIT TRANSACTION
Run Code Online (Sandbox Code Playgroud)
是否有可能在此期间BEGIN TRANS; UPDATE; COMMIT
另一个进程可以同时读取新旧值?
我想知道这一点,因为更新不会只在一个地方改变一个值。该值将存在于多个地方:
如果更新开始,它必须更新所有这些位置的值。如果另一个进程使用索引执行计划来查找值会发生什么:
在那一刻,如果发出一个查询来查看聚集索引中的值,它将找到Waiting。
但是如果一个查询使用IX_Transactions_4
它会发现一个值Pending
。
内部的锁顺序没有记录,但我假设 SQL Server 对聚集和非聚集索引采用共享锁:
然后将这些锁升级为更新锁:
此时,另一个进程仍然可以读取这5个索引中的值;因为选择与更新锁兼容。
然后更新继续。如果我们在瞬间拍一张照片:
另一个进程不能再从具有排他锁的项目中读取值,但它仍然可以从仍然具有更新锁的项目中读取值(共享锁与更新锁兼容)
这仅在 SQL 仅根据需要升级为独占时才有效。如果相反,它会:
我不能再打字了;我继续的意志被粉碎了。就像我说的,我正在抓紧稻草。
您可以使用跟踪标志1200
打印出锁定信息和3916
打印日志信息。(两个无证AFAIK)
对于以下测试设置
CREATE TABLE T
(
A INT IDENTITY PRIMARY KEY,
B INT
)
CREATE NONCLUSTERED INDEX IX ON T(B)
INSERT INTO T
SELECT number
FROM master..spt_values
Run Code Online (Sandbox Code Playgroud)
然后运行以下两次(因为第二次运行不包含编译期间获取的无关锁)
DBCC TRACEON(3604,1200,3916,-1) WITH NO_INFOMSGS
SET NOCOUNT ON;
UPDATE T
SET B += 1
WHERE A = 1000
/*If you habitually have 3604 on then change the below*/
DBCC TRACEOFF(3604,1200,3916,-1) WITH NO_INFOMSGS
Run Code Online (Sandbox Code Playgroud)
这将返回以下输出
进程 54 在 OBJECT 上获取 IX 锁:11:245575913:0(class bit2000000 ref1)结果:OK
进程 54 在 PAGE 上获取 IU 锁:11:1:127(class bit0 ref1)结果:OK
进程54获取KEY上的U锁:11:72057594039042048 (1f00de11a529) (class bit0 ref1) 结果:OK
进程 54 在 PAGE: 11:1:127 (class bit2000000 ref0) 上获取 IX 锁结果:OK
进程54获取KEY上的X锁:11:72057594039042048 (1f00de11a529) (class bit2000000 ref0) 结果:OK
XdesId 0000:00000423: m_logReserved 9162, delta 9162, op LOP_BEGIN_XACT, caller GenerateLogRec, LSN 00000027:00000159:0001。
XdesId 0000:00000423: m_logReserved 9379, delta 217, op LOP_MODIFY_ROW, caller GenerateLogRec, LSN 00000027:00000159:0002。
进程 54 在 PAGE: 11:1:166 (class bit2000000 ref1) 上获取 IX 锁结果:OK
进程54获取KEY上的X锁:11:72057594039107584 (dbf572a551f4) (class bit2000000 ref1) 结果:OK
XdesId 0000:00000423: m_logReserved 9597, delta 218, op LOP_DELETE_ROWS, caller GenerateLogRec, LSN 00000027:00000159:0003。
进程 54 在 PAGE: 11:1:166 (class bit2000000 ref0) 上获取 IX 锁结果:OK
进程54获取KEY上的RangeI-N锁:11:72057594039107584 (a39688da7d04) (class bit1000000 ref1) 结果:OK
进程54获取KEY上的X锁:11:72057594039107584 (82cbfa4b30c1) (class bit2000000 ref0) 结果:OK
XdesId 0000:00000423: m_logReserved 9671, delta 74, op LOP_INSERT_ROWS, caller GenerateLogRec, LSN 00000027:00000159:0004。
进程 54 在 KEY 上释放锁引用:11:72057594039107584 (dbf572a551f4)
进程 54 在 PAGE 上释放锁定引用:11:1:166
进程 54 在 KEY 上释放锁定引用:11:72057594039042048 (1f00de11a529)
进程 54 在 PAGE 上释放锁定引用:11:1:127
XdesId 0000:00000423: m_logReserved 0, delta -9671, op LOP_COMMIT_XACT, caller GenerateLogRec, LSN 00000027:00000159:0005。
非聚集索引 ( 1:166
)上没有锁定,直到在LOP_MODIFY_ROW
聚集索引键上更新之后(在页面上1:127
),所以是的,这将是可能的。
以上是一个狭窄的(每行)更新计划。
使用广泛的(每个索引)计划,每个索引都会依次更新。可能会进行排序操作,如下面的计划所示。
这将使机会窗口更宽,但是,虽然事务可能从尚未更新的索引中读取“旧”值,但读提交事务不可能读取该值的“新”版本,直到事务已提交。
但是,读提交语句当然可以读取相同值的两个不同提交版本。
在一个连接运行
WHILE ( 1 = 1 )
BEGIN
UPDATE T
SET B += 1
WHERE A = 1000;
END
Run Code Online (Sandbox Code Playgroud)
然后在另一个运行中
SELECT *
FROM T T1 WITH(READCOMMITTEDLOCK )
INNER HASH JOIN T T2 WITH(READCOMMITTEDLOCK )
ON T1.A + 0 = T2.A + 0
WHERE T1.A = 1000
AND T2.A = 1000
Run Code Online (Sandbox Code Playgroud)
对我来说,大约 50% 的时间第二个查询返回具有不同B
值的行,因为两个查找之间的值发生变化T
。