SQL 更新中的服务器并发
SQL Server Concurrency in update
我有一个 TABLE:
id status mod runat
1 0 null null
2 0 null null
3 0 null null
4 0 null null
而且,我同时两次调用此查询。
UPDATE TABLE
SET
status = 1,
mod = GETDATE()
OUTPUT INSERTED.id
WHERE id = (
SELECT TOP (1) id
FROM TABLE
WHERE STATUS = 0
AND NOT EXISTS(SELECT * FROM TABLE WHERE STATUS = 1)
AND COALESCE(runat, GETDATE()) <= GETDATE()
ORDER BY ID ASC)
而且...有时我有:
1
1
相反
1
NULL
为什么?更新查询不是事务性的?
简答
将 WITH (UPDLOCK, HOLDLOCK) 添加到 select
UPDATE TABLE
SET
status = 1,
mod = GETDATE()
OUTPUT INSERTED.id
WHERE id = (
SELECT TOP (1) id
FROM TABLE WITH (UPDLOCK, HOLDLOCK)
WHERE STATUS = 0
AND NOT EXISTS(SELECT * FROM TABLE WHERE STATUS = 1)
AND COALESCE(runat, GETDATE()) <= GETDATE()
ORDER BY ID ASC)
说明
因为您使用子查询来获取 ID,所以这里基本上有两个语句 运行 - select 和一个更新。当 1 返回两次时,它仅表示在任一更新完成之前都 select 语句 运行 。如果您添加一个 UPDLOCK,那么当第一个 运行s 它持有 UPDLOCK。第二个 SELECT 必须等待第一个 select 释放 UPDLOCK 才能执行。
更多信息
究竟发生了什么取决于数据库的锁定方案,以及其他语句发出的锁。这种更新在某些情况下甚至会导致死锁。
因为语句 运行 太快了,很难看出它们持有什么锁。要有效地减慢速度,一个好技巧是
- 打开一个会话,运行 第一个语句带有 BEGIN TRANS
它开头的语句(不包括 COMMIT 或 ROLLBACK)
- 运行 在 sys.dm_tran_locks 上查询以查看正在持有的锁
- 打开第二个会话和运行第二个语句,看看是什么
发生。如果您的锁定方案设置正确,它应该等待
第一个在它做任何事情之前完成。
- 切换回第一个会话并提交以模拟完成
link 包含很多信息,但锁定和数据争用是复杂的领域,有很多可能的解决方案。 link 应该为您提供决定如何处理此问题所需的一切信息。
https://docs.microsoft.com/en-us/sql/relational-databases/sql-server- t运行saction-locking-and-row-versioning-guide?view=sql-server-2017
我有一个 TABLE:
id status mod runat
1 0 null null
2 0 null null
3 0 null null
4 0 null null
而且,我同时两次调用此查询。
UPDATE TABLE
SET
status = 1,
mod = GETDATE()
OUTPUT INSERTED.id
WHERE id = (
SELECT TOP (1) id
FROM TABLE
WHERE STATUS = 0
AND NOT EXISTS(SELECT * FROM TABLE WHERE STATUS = 1)
AND COALESCE(runat, GETDATE()) <= GETDATE()
ORDER BY ID ASC)
而且...有时我有:
1
1
相反
1
NULL
为什么?更新查询不是事务性的?
简答
将 WITH (UPDLOCK, HOLDLOCK) 添加到 select
UPDATE TABLE
SET
status = 1,
mod = GETDATE()
OUTPUT INSERTED.id
WHERE id = (
SELECT TOP (1) id
FROM TABLE WITH (UPDLOCK, HOLDLOCK)
WHERE STATUS = 0
AND NOT EXISTS(SELECT * FROM TABLE WHERE STATUS = 1)
AND COALESCE(runat, GETDATE()) <= GETDATE()
ORDER BY ID ASC)
说明
因为您使用子查询来获取 ID,所以这里基本上有两个语句 运行 - select 和一个更新。当 1 返回两次时,它仅表示在任一更新完成之前都 select 语句 运行 。如果您添加一个 UPDLOCK,那么当第一个 运行s 它持有 UPDLOCK。第二个 SELECT 必须等待第一个 select 释放 UPDLOCK 才能执行。
更多信息
究竟发生了什么取决于数据库的锁定方案,以及其他语句发出的锁。这种更新在某些情况下甚至会导致死锁。
因为语句 运行 太快了,很难看出它们持有什么锁。要有效地减慢速度,一个好技巧是
- 打开一个会话,运行 第一个语句带有 BEGIN TRANS 它开头的语句(不包括 COMMIT 或 ROLLBACK)
- 运行 在 sys.dm_tran_locks 上查询以查看正在持有的锁
- 打开第二个会话和运行第二个语句,看看是什么 发生。如果您的锁定方案设置正确,它应该等待 第一个在它做任何事情之前完成。
- 切换回第一个会话并提交以模拟完成
link 包含很多信息,但锁定和数据争用是复杂的领域,有很多可能的解决方案。 link 应该为您提供决定如何处理此问题所需的一切信息。 https://docs.microsoft.com/en-us/sql/relational-databases/sql-server- t运行saction-locking-and-row-versioning-guide?view=sql-server-2017