如何在 SQL 服务器中共享锁定尚不存在的 SELECT 上的一行?
How to shared-lock a row on a SELECT that doesn't yet exist, in SQL Server?
假设我有一个操作,在事务中检查是否存在 ID 大于 (later/newer) 其自身 ID 的“日志”行。如果它不存在,它会执行一些其他的 UPDATE 操作,然后插入日志 ID。如果它确实存在,它只是插入没有更新的日志 ID。类似于:
BEGIN TRAN
SELECT TOP 1 UpdateLogId
FROM UpdateLog
WHERE SourceRecordPrimaryKey = @SourceRecordPrimaryKey
AND UpdateLogId > @changeNumber;
-- then, only if that doesn't exist:
UPDATE SourceTable SET ... WHERE SourceTableId=@SourceRecordPrimaryKey;
-- then, in either case:
INSERT UpdateLog (UpdateLogId, SourceRecordPrimaryKey)
VALUES (@changeNumber,@SourceRecordPrimaryKey)
现在,如果我有两个并发进程,它们可能正在处理两个不同的传入操作,但是这两个进程的日志行都不存在,那么这两个进程可能 运行 几乎同时发生,两者都检查事务中的后续日志行,都发现(因为没有行供另一个锁定)它不存在,并且都在竞争条件下执行。
在 SQL 服务器中是否有通用的方法来处理此问题,以便这两个中的一个会阻止另一个?这几乎就像我想要一个尚不存在的行上的共享锁,因为我计划稍后在事务中创建它,并且我希望其他进程在检查它是否存在时阻塞。这里有我缺少的概念吗?
假设我正确理解你的问题,一种方法是显式定义更新并 table 锁定 SELECT
,这将阻止另一个 SELECT
能够读取 table 直到另一笔交易完成。
你可以用类似下面的东西来复制它。首先 运行 下面的查询 window (交易有意保持打开状态)。
CREATE TABLE dbo.SomeTable (ID int IDENTITY);
GO
INSERT INTO dbo.SomeTable
DEFAULT VALUES
GO 10
BEGIN TRANSACTION
IF EXISTS(SELECT 1
FROM dbo.SomeTable WITH (UPDLOCK,TABLOCK)
WHERE ID = 11) --An ID that doesn't exists
PRINT N'Do UPDATE operation';
然后,如果您打开一个新查询 window 并尝试 运行 一个针对 table 的简单 SELECT
,该查询将会成功,因为 table 被锁定。如果您随后通过 运行ning 以下内容完成上述查询,则另一个 SELECT
将 运行 返回附加行:
INSERT INTO dbo.SomeTable
DEFAULT VALUES;
COMMIT
要锁定不存在的行,您需要一个键范围锁,您可以使用 HOLDLOCK 或 SERIALIZABLE 锁提示获得它。要使两个会话在键或键范围锁上相互阻塞,请使用 UPDLOCK 提示。 UPDLOCK 强制 U 锁阻止其他 U 锁而不是 S 锁。
BEGIN TRANSACTION
SELECT TOP 1 UpdateLogId
FROM UpdateLog with (UPDLOCK,HOLDLOCK)
WHERE SourceRecordPrimaryKey = @SourceRecordPrimaryKey
AND UpdateLogId > @changeNumber;
-- then, only if that doesn't exist:
UPDATE SourceTable SET ... WHERE SourceTableId=@SourceRecordPrimaryKey;
-- then, in either case:
INSERT UpdateLog (UpdateLogId, SourceRecordPrimaryKey)
VALUES (@changeNumber,@SourceRecordPrimaryKey)
COMMIT TRANSACTION
假设我有一个操作,在事务中检查是否存在 ID 大于 (later/newer) 其自身 ID 的“日志”行。如果它不存在,它会执行一些其他的 UPDATE 操作,然后插入日志 ID。如果它确实存在,它只是插入没有更新的日志 ID。类似于:
BEGIN TRAN
SELECT TOP 1 UpdateLogId
FROM UpdateLog
WHERE SourceRecordPrimaryKey = @SourceRecordPrimaryKey
AND UpdateLogId > @changeNumber;
-- then, only if that doesn't exist:
UPDATE SourceTable SET ... WHERE SourceTableId=@SourceRecordPrimaryKey;
-- then, in either case:
INSERT UpdateLog (UpdateLogId, SourceRecordPrimaryKey)
VALUES (@changeNumber,@SourceRecordPrimaryKey)
现在,如果我有两个并发进程,它们可能正在处理两个不同的传入操作,但是这两个进程的日志行都不存在,那么这两个进程可能 运行 几乎同时发生,两者都检查事务中的后续日志行,都发现(因为没有行供另一个锁定)它不存在,并且都在竞争条件下执行。
在 SQL 服务器中是否有通用的方法来处理此问题,以便这两个中的一个会阻止另一个?这几乎就像我想要一个尚不存在的行上的共享锁,因为我计划稍后在事务中创建它,并且我希望其他进程在检查它是否存在时阻塞。这里有我缺少的概念吗?
假设我正确理解你的问题,一种方法是显式定义更新并 table 锁定 SELECT
,这将阻止另一个 SELECT
能够读取 table 直到另一笔交易完成。
你可以用类似下面的东西来复制它。首先 运行 下面的查询 window (交易有意保持打开状态)。
CREATE TABLE dbo.SomeTable (ID int IDENTITY);
GO
INSERT INTO dbo.SomeTable
DEFAULT VALUES
GO 10
BEGIN TRANSACTION
IF EXISTS(SELECT 1
FROM dbo.SomeTable WITH (UPDLOCK,TABLOCK)
WHERE ID = 11) --An ID that doesn't exists
PRINT N'Do UPDATE operation';
然后,如果您打开一个新查询 window 并尝试 运行 一个针对 table 的简单 SELECT
,该查询将会成功,因为 table 被锁定。如果您随后通过 运行ning 以下内容完成上述查询,则另一个 SELECT
将 运行 返回附加行:
INSERT INTO dbo.SomeTable
DEFAULT VALUES;
COMMIT
要锁定不存在的行,您需要一个键范围锁,您可以使用 HOLDLOCK 或 SERIALIZABLE 锁提示获得它。要使两个会话在键或键范围锁上相互阻塞,请使用 UPDLOCK 提示。 UPDLOCK 强制 U 锁阻止其他 U 锁而不是 S 锁。
BEGIN TRANSACTION
SELECT TOP 1 UpdateLogId
FROM UpdateLog with (UPDLOCK,HOLDLOCK)
WHERE SourceRecordPrimaryKey = @SourceRecordPrimaryKey
AND UpdateLogId > @changeNumber;
-- then, only if that doesn't exist:
UPDATE SourceTable SET ... WHERE SourceTableId=@SourceRecordPrimaryKey;
-- then, in either case:
INSERT UpdateLog (UpdateLogId, SourceRecordPrimaryKey)
VALUES (@changeNumber,@SourceRecordPrimaryKey)
COMMIT TRANSACTION