在我最近的问题中.. 我注意到死锁会杀死更新语句而不是锁定在其中的选择命令。
SELECT 的隔离级别是数据库默认值,即读提交快照隔离。
SELECT 是我们 ETL 中事务的一部分。
是的,SELECT 需要锁,如果它们参与到一个已经完成大量工作的事务中,它们就可以赢得死锁。
要查看它,请进入打开 RCSI 的数据库,并创建几个表 - 只有其中一个表中有数据:
CREATE TABLE dbo.Table1 (ID INT IDENTITY(1,1), Things VARCHAR(50));
GO
CREATE TABLE dbo.Table2 (ID INT IDENTITY(1,1), Things VARCHAR(50));
GO
/* Only Table1 will have data: */
INSERT INTO dbo.Table1 (Things)
SELECT 'Meaningless Stuff'
FROM sys.all_columns
GO
Run Code Online (Sandbox Code Playgroud)
然后启动一个事务,该事务在带有数据的表上做了大量工作:
/* WINDOW #1: Start a transaction that does a lot of work */
BEGIN TRAN
UPDATE dbo.Table1
SET Things = 'Different Stuff';
GO
Run Code Online (Sandbox Code Playgroud)
现在在 SSMS 中启动一个新选项卡,以便您处于不同的会话中 - 我们将调用此窗口 #2 - 并运行:
/* WINDOW #2: Start a transaction that does
just a little work on an empty table: */
BEGIN TRAN
ALTER TABLE dbo.Table2 ADD NewCol VARCHAR(10);
GO
Run Code Online (Sandbox Code Playgroud)
两个不同窗口中的两个事务都在两个不同的表上工作。到现在为止还挺好。现在继续在窗口 #2 中,在窗口 #1 的表上运行更新:
/* WINDOW #2: Everything's cool. Now try an update that gets blocked: */
UPDATE dbo.Table1 SET Things = 'Even more different stuff';
Run Code Online (Sandbox Code Playgroud)
该更新只是挂起 - 他被阻止了。不要取消他 - 让他跑。他被 Window #1 事务阻止了。在 RCSI 中,编写器在尝试处理相同行时仍会相互阻塞。
切换回窗口 #1,然后运行:
/* WINDOW #1: Run a select */
SELECT * FROM dbo.Table2;
Run Code Online (Sandbox Code Playgroud)
并数到5。5秒内,SQL Server唤醒死锁监视器,环顾四周,发现这两个查询相互阻塞,并且……选择获胜!选择返回数据,更新窗口显示:
Msg 1205, Level 13, State 56, Line 9
Transaction (Process ID 54) was deadlocked on lock resources
with another process and has been chosen as the deadlock victim.
Rerun the transaction.
Run Code Online (Sandbox Code Playgroud)
为什么?因为 SQL Server 默认选择最容易回滚的事务作为受害者。在我们的例子中,窗口 #1 已经做了很多工作,更新了数千行,而窗口 #2 只做了很少的工作。
这是一个人为的示例,目的是快速教授课程 - 您的场景可能不涉及 ALTER TABLE 命令。尽管如此,同样的理论仍然适用。