在 SQL Server 中,如何以类似于 Oracle 的“SELECT FOR UPDATE WAIT”的方式锁定单行?

Posted

技术标签:

【中文标题】在 SQL Server 中,如何以类似于 Oracle 的“SELECT FOR UPDATE WAIT”的方式锁定单行?【英文标题】:In SQL Server, how can I lock a single row in a way similar to Oracle's "SELECT FOR UPDATE WAIT"? 【发布时间】:2012-03-19 03:00:00 【问题描述】:

我有一个连接到 Oracle 数据库并对其执行操作的程序。我现在想调整该程序以支持 SQL Server 数据库。

在 Oracle 版本中,我使用“SELECT FOR UPDATE WAIT”来锁定我需要的特定行。我在更新基于 SELECT 的结果并且其他会话绝对不能同时修改它的情况下使用它,因此他们必须先手动锁定它。系统很容易受到试图同时访问相同数据的会话的影响。

例如: 两个用户尝试获取数据库中具有最高优先级的行,将其标记为忙碌,对其执行操作,并将其再次标记为可用以供以后使用。 在 Oracle 中,逻辑基本上是这样的:

BEGIN TRANSACTION;
SELECT ITEM_ID FROM TABLE_ITEM WHERE ITEM_PRIORITY > 10 AND ITEM_CATEGORY = 'CT1'
    ITEM_STATUS = 'available' AND ROWNUM = 1 FOR UPDATE WAIT 5;
UPDATE [locked item_id] SET ITEM_STATUS = 'unavailable';
COMMIT TRANSACTION;

请注意,查询是在我的代码中动态构建的。另请注意,当先前最有利的行被标记为不可用时,第二个用户将自动转到下一个,依此类推。此外,处理不同类别的不同用户不必等待彼此的锁被释放。最坏最坏,5秒后返回错误并取消操作。

最后,问题是:如何在 SQL Server 中获得相同的结果?我一直在研究锁定提示,从理论上讲,它们似乎应该起作用。但是,阻止其他锁的唯一锁是“UPDLOCK”和“XLOCK”,它们都只能在表级别工作。 那些在行级别起作用的锁定提示都是共享锁,这也不能满足我的需求(两个用户可以同时锁定同一行,都将其标记为不可用并对相应的项执行冗余操作)。

有些人似乎添加了“时间修改”列,以便会话可以验证他们是修改它的人,但这听起来会有很多冗余和不必要的访问。

【问题讨论】:

【参考方案1】:

您可能正在寻找with (updlock, holdlock)。这将使select 获取更新所需的exclusive 锁,而不是shared 锁。 holdlock 提示告诉 SQL Server 保持锁定直到事务结束。

FROM TABLE_ITEM with (updlock, holdlock)

【讨论】:

我同意这是我正在寻找的锁类型,但是,正如我在问题中所述,updlock 会影响整个表,而我希望只影响一行。 updlock 更改锁定的类型(独占与共享),但不更改锁定的类型。要锁定整个表,指定with (tablock),或锁定一行,指定with (rowlock)【参考方案2】:

正如documentation 所说:

XLOCK

指定排他锁将被占用并保持到 交易完成。如果使用 ROWLOCK、PAGLOCK 或 TABLOCK 指定, 排他锁适用于适当的粒度级别。

所以解决方案是使用WITH(XLOCK, ROWLOCK):

BEGIN TRANSACTION;

SELECT ITEM_ID
FROM TABLE_ITEM
WITH(XLOCK, ROWLOCK)
WHERE ITEM_PRIORITY > 10 AND ITEM_CATEGORY = 'CT1' AND ITEM_STATUS = 'available' AND ROWNUM = 1;

UPDATE [locked item_id] SET ITEM_STATUS = 'unavailable';

COMMIT TRANSACTION;

【讨论】:

性能影响是什么?您认为这会导致死锁吗?【参考方案3】:

在 SQL Server 中有锁定提示,但它们不像您提供的 Oracle 示例那样跨越它们的语句。在 SQL Server 中执行此操作的方法是在包含要执行的语句的事务上设置隔离级别。请参阅this MSDN page,但总体结构类似于:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

    select * from ...

    update ...

COMMIT TRANSACTION;

SERIALIZABLE 是最高的隔离级别。有关其他选项,请参阅链接。来自 MSDN:

SERIALIZABLE 指定以下内容:

语句无法读取已修改但尚未修改的数据 由其他事务提交。

没有其他事务可以修改已被 当前事务直到当前事务完成。

其他事务不能插入带有键值的新行 落在当前任何语句读取的键范围内 交易直到当前交易完成。

【讨论】:

很高兴知道,但似乎 Serializable 仅在数据已被修改时才会阻止读取,因此直到那一刻才会阻止锁定。 @Paradoxyde:这不是我的理解。根据规范的第二项(参见我上面的编辑),该行在被读取后立即被锁定,直到事务结束。您的问题来自规范的哪一部分? 这获取了一个共享锁,后来又获取了一个排他锁,所以它很容易出现死锁 你一定误解了我之前的评论。我所说/意思是,虽然其他事务不能修改数据(如您引用部分的文档中所述),但它们仍然可以获得锁。这意味着两个会话可以同时锁定同一行,一个会等待第一个会话将其项目设置为“不可用”。当第一个会话释放该行时,第二个会话也将更新该行以将其设置为“不可用”。这导致两个会话相信他们拥有该项目的所有权。 好吧,在玩了一会儿之后,我找到了一个潜在的解决方案,尽管我承认它感觉便宜并且在某些情况下可能不可靠(SQL Server 并不总是支持行锁)。我所做的是将我的事务隔离级别设置为读取已提交,并开始我的事务。在进行选择以获取我最喜欢的行的索引之前,我使用相同的搜索参数进行更新并且什么都不更新(例如设置 item_name = item_name)。这有效地锁定了我的行,直到事务结束。任何其他访问此表的事务都可以指定 readpast 或 nolock。【参考方案4】:

您是否尝试过WITH (ROWLOCK)

BEGIN TRAN

   UPDATE your_table WITH (ROWLOCK)
   SET your_field = a_value
   WHERE <a predicate>

COMMIT TRAN

【讨论】:

ROWLOCK 确实会阻止两个用户修改同一行,但它不会阻止两个用户锁定同一行。 @Paradoxyde 如果两个人锁定同一行,会不会是死锁。行更新会成功吗?

以上是关于在 SQL Server 中,如何以类似于 Oracle 的“SELECT FOR UPDATE WAIT”的方式锁定单行?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 MySQL 中打破类似于 MS SQL Server 的列名?

如何在 SQL Server [2005] 中创建类似于 .dbo 的架构

如何选择列名并为 SQL Server 获取数据?

如何在 SQL Server 中查看变量(列)的属性 [关闭]

如何编写一个查询以从SQL Server中包含类似名称的多个表中获取数据

在SQL Server和Postgresql中使用CASE作为类似于枢轴的函数