【问题标题】:SELECT FOR UPDATE with SQL Server使用 SQL Server 选择更新
【发布时间】:2010-12-01 19:23:25
【问题描述】:

我正在使用隔离级别为READ_COMMITTEDREAD_COMMITTED_SNAPSHOT=ON 的Microsoft SQL Server 2005 数据库。

现在我想使用:

SELECT * FROM <tablename> FOR UPDATE

...以便其他数据库连接在尝试访问同一行“FOR UPDATE”时阻塞。

我试过了:

SELECT * FROM <tablename> WITH (updlock) WHERE id=1

...但这会阻止所有其他连接,即使选择“1”以外的 id。

对于 Oracle、DB2、MySql,执行 SELECT FOR UPDATE 的正确提示是什么?

编辑 2009-10-03:

这些是创建表和索引的语句:

CREATE TABLE example ( Id BIGINT NOT NULL, TransactionId BIGINT, 
    Terminal BIGINT, Status SMALLINT );
ALTER TABLE example ADD CONSTRAINT index108 PRIMARY KEY ( Id )
CREATE INDEX I108_FkTerminal ON example ( Terminal )
CREATE INDEX I108_Key ON example ( TransactionId )

很多并行进程都这样做SELECT

SELECT * FROM example o WITH (updlock) WHERE o.TransactionId = ?

编辑 2009-10-05:

为了更好地了解我在下表中记录了所有尝试过的解决方案:

机制 |在不同的行块上选择 |在同一行块上选择 ------------------------------------+-------------- ------+-------------- 行锁 |没有 |不 更新锁、行锁 |是的 |是的 xlock,rowlock |是的 |是的 可重复阅读 |没有 |不 DBCC TRACEON (1211,-1) |是的 |是的 行锁,xlock,holdlock |是的 |是的 上锁,挂锁| 高分辨率照片| CLIPARTO是的 |是的 上锁,阅读过去 |没有 |不 我在寻找 |没有 |是的

【问题讨论】:

  • 我不仅在寻找优化器提示。另一种可能的解决方案是更改隔离级别、全局数据库属性……一切(但使用不同的数据库)都是可能的。
  • 你想做什么需要这样的锁定。通常最好用正确的查询而不是服务器的“功能”来解决
  • 您能否提供您正在使用的查询和表的 DDL,包括任何键和索引。
  • 你确定你的其他查询不是事务隔离读取未提交?
  • 作为一种解决方法,您可以尝试先对此行进行简单更新(而不真正更改任何数据)。之后,您可以继续选择 in 中的行进行更新。

标签: sql sql-server sql-server-2005 tsql read-committed-snapshot


【解决方案1】:

最近我有一个deadlock problem,因为 Sql Server 锁定的次数超过了必要的次数(页面)。你真的不能对它做任何事情。现在我们正在捕获死锁异常......我希望我有 Oracle。

编辑: 我们同时使用快照隔离,它解决了很多但不是全部的问题。不幸的是,为了能够使用快照隔离,它必须得到数据库服务器的允许,这可能会在客户站点造成不必要的问题。现在我们不仅要捕获死锁异常(当然,这仍然可能发生),而且还要捕获并发问题以从后台进程重复事务(用户不能重复)。但这仍然比以前好很多。

【讨论】:

  • 恐怕这是真的。我发现无法获得有效的“SELECT FOR UPDATE”。我现在的解决方案是放弃“SELECT FOR UPDATE”并执行简单的非阻塞“SELECT”并使用更新计数器(“UPDATE WHERE id=? and updateCount=?”)检查并发“UPDATES”。跨度>
  • SQL 2008 提供了两个新的乐观隔离级别,类似于 Oracle 提供的
  • @ChrisBednarski 我在 SQL Server 2008 R2 中使用了 WITH(ROWLOCK),但它仍然锁定了不止一行!!!你能解释一下吗
  • @bjan:查找快照隔离。这里有一些信息msdn.microsoft.com/en-us/library/tcbchxcb(v=vs.80).aspx
  • @ChrisBednarski 感谢您的链接,这两个命令使白天和黑夜有所不同:ALTER DATABASE MyDatabase SET ALLOW_SNAPSHOT_ISOLATION ON; ALTER DATABASE MyDatabase SET READ_COMMITTED_SNAPSHOT ON;另请参阅 MichaelBuen 对此问题的回答。
【解决方案2】:

我有类似的问题,我只想锁定 1 行。 据我所知,使用UPDLOCK 选项,SQLSERVER 会锁定它需要读取的所有行以获取该行。因此,如果您没有定义直接访问该行的索引,则所有前面的行都将被锁定。 在您的示例中:

假设您有一个名为 TBL 的表,其中包含 id 字段。 您想用id=10 锁定行。 您需要为字段 id(或您选择的任何其他字段)定义索引:

CREATE INDEX TBLINDEX ON TBL ( id )

然后,您只锁定您读取的行的查询是:

SELECT * FROM TBL WITH (UPDLOCK, INDEX(TBLINDEX)) WHERE id=10.

如果不使用 INDEX(TBLINDEX) 选项,SQLSERVER 需要从表的开头读取所有行以找到您的带有id=10 的行,因此这些行将被锁定。

【讨论】:

  • +1 用于提供根本原因,为什么 SQL Server“忽略”rowlock 指令。我在我的应用程序下使用了这种方法,实际上我已经实现了 tangens 的要求
  • -1 我有一个聚集索引,在提示中包含它没有任何区别。 updlock+rowlock+holdlock 工作得很好,一旦我意识到我错误地触摸了我 sp 中锁定 SELECT 语句之外的其他记录(缺少原始选择中存在的 ids 条件)。
【解决方案3】:

也许使 mvcc 永久化可以解决它(与仅特定批次相反:SET TRANSACTION ISOLATION LEVEL SNAPSHOT):

ALTER DATABASE yourDbNameHere SET READ_COMMITTED_SNAPSHOT ON;

[编辑:10 月 14 日]

阅读后:Better concurrency in Oracle than SQL Server? 和此:http://msdn.microsoft.com/en-us/library/ms175095.aspx

当 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON,则 用于支持该选项的机制 立即激活。什么时候 设置 READ_COMMITTED_SNAPSHOT 选项,仅执行连接 允许使用 ALTER DATABASE 命令 在数据库中。必须没有 数据库中其他打开的连接 直到 ALTER DATABASE 完成。这 数据库不必在 单用户模式。

我得出的结论是,您需要设置两个标志才能在给定数据库上永久激活 mssql 的 MVCC:

ALTER DATABASE yourDbNameHere SET ALLOW_SNAPSHOT_ISOLATION ON;
ALTER DATABASE yourDbNameHere SET READ_COMMITTED_SNAPSHOT ON;

【讨论】:

  • 在阅读了@Chris Bednarski 在接受的答案下的评论后,我得出了和你一样的结论,迈克尔。这似乎大大缓解了我们的僵局问题。如果您使用 SQL Server Management Studio 中的数据库属性对话框,系统会提示您断开所有客户端,并且无需重新启动。
【解决方案4】:

先尝试对此行进行简单更新(不真正更改任何数据)如何?之后,您可以继续选择 in 中的行进行更新。

UPDATE dbo.Customer SET FieldForLock = FieldForLock WHERE CustomerID = @CustomerID
/* do whatever you want */

编辑:您当然应该将其包装在事务中

编辑 2:另一种解决方案是使用 SERIALIZABLE 隔离级别

【讨论】:

  • 此解决方案与 Feu 2011 年 6 月 14 日之前提供的解决方案相同。没有积分。
【解决方案5】:

创建一个虚假更新来强制执行行锁。

UPDATE <tablename> (ROWLOCK) SET <somecolumn> = <somecolumn> WHERE id=1

如果这没有锁定你的行,天知道会发生什么。

在此“UPDATE”之后,您可以进行SELECT (ROWLOCK) 和后续更新。

【讨论】:

  • 我想知道这是否比替代方案更糟糕,即只使用 xlock 提示进行选择。 Rowlock 不一定可靠,因为 SQL Server 实际上并不锁定行,它锁定行的 6 字节散列,因此有可能与表中的大量记录发生冲突。此外,因为 rowlock 是一个“提示”,它并不能保证不会发生锁升级,尽管您可以通过禁用表上的锁升级来最大限度地减少这种风险。
【解决方案6】:

我以完全不同的方式解决了行锁问题。我意识到 sql server 无法以令人满意的方式管理这样的锁。我选择从编程的角度通过使用互斥锁来解决这个问题...waitForLock...releaseLock...

【讨论】:

  • 只要只有一台服务器需要更新数据库,它就可以工作。
【解决方案7】:

应用程序锁定是一种使用自定义粒度滚动您自己的锁定同时避免“有用”锁定升级的方法。见sp_getapplock

【讨论】:

  • 不幸的是,应用锁在解决死锁问题时并不总是有帮助。无论您使用 sp_getapplock 锁定什么,当您实际开始修改数据库记录时,您都必须面对这样一个事实:引擎开始锁定行,并且由于可能发生冲突的页或表锁定升级而无意中锁定了不相关的行与并发事务。最好禁用锁升级并指定排他行锁提示,并按照定义的锁定顺序锁定行,以尽可能避免死锁。
  • 我宁愿采用乐观的锁定方法来创建行版本,因为它在 ORM/持久层(如 Hibernate)中完成,请参阅:codippa.com/… 这假设一个受控环境,其中更新通过特定应用程序,或者多个应用程序采用相同的方法。本文涵盖了大多数场景,包括 SQL Server 的特定 'rowversion' 数据类型和您自己的 sp_getapplock 建议:simple-talk.com/sql/t-sql-programming/…
【解决方案8】:

问题 - 这种情况是否被证明是锁升级的结果(即,如果您使用探查器跟踪锁升级事件,那肯定是发生了什么导致阻塞)?如果是这样,有一个完整的解释和一个(相当极端的)解决方法,通过在实例级别启用跟踪标志来防止锁升级。见http://support.microsoft.com/kb/323630跟踪标志1211

但是,这可能会产生意想不到的副作用。

如果您有意锁定一行并使其锁定较长时间,那么对事务使用内部锁定机制并不是最好的方法(至少在 SQL Server 中)。 SQL Server 中的所有优化都是针对短事务的——进入、更新、退出。这首先是锁升级的原因。

因此,如果打算长时间“签出”一行,而不是事务锁定,最好使用具有值的列和普通的更新语句来将行标记为锁定与否。

【讨论】:

  • 同意,尽管我宁愿采用乐观的锁定方法来创建行版本,就像在 ORM 的/持久层(如 Hibernate)中所做的那样,请参阅:codippa.com/… 这假设一个受控环境,其中更新通过一个特定的应用程序,或者多个应用程序采用相同的方法。本文涵盖了大多数场景,包括 SQL Server 特定的“rowversion”数据类型:simple-talk.com/sql/t-sql-programming/…
【解决方案9】:

我假设您不希望任何其他会话能够在此特定查询运行时读取该行...

使用 WITH (XLOCK,READPAST) 锁定提示将您的 SELECT 包装在事务中将获得您想要的结果。只要确保那些其他并发读取没有使用 WITH (NOLOCK)。 READPAST 允许其他会话在其他行上执行相同的 SELECT。

BEGIN TRAN
  SELECT *
  FROM <tablename> WITH (XLOCK,READPAST) 
  WHERE RowId = @SomeId

  -- Do SOMETHING

  UPDATE <tablename>
  SET <column>=@somevalue
  WHERE RowId=@SomeId
COMMIT

【讨论】:

    【解决方案10】:

    您必须在提交时处理异常并重复事务。

    【讨论】:

      【解决方案11】:

      好的,默认情况下,单个选择将使用“已提交读”事务隔离,该隔离会锁定并因此停止对该集合的写入。您可以使用

      更改事务隔离级别
      Set Transaction Isolation Level { Read Uncommitted | Read Committed | Repeatable Read | Serializable }
      Begin Tran
        Select ...
      Commit Tran
      

      这些在 SQL Server BOL 中有详细解释

      您的下一个问题是,默认情况下,如果您有超过 ~2500 个锁或在锁事务中使用超过 40% 的“正常”内存,SQL Server 2K5 将升级锁。升级到页面,然后是表锁定

      您可以通过设置“跟踪标志”1211t 来关闭此升级,有关详细信息,请参阅 BOL

      【讨论】:

      • 非常感谢,这听起来可能是解决方案。我会在星期一试试。
      • 我试过了,但是没有用。访问不同的行时,第二个 SELECT 仍会阻塞。
      【解决方案12】:

      你试过 READPAST 吗?

      在将表视为队列时,我将 UPDLOCK 和 READPAST 一起使用。

      【讨论】:

      • 我试过了,但是第二次选择没有找到记录。这不是我正在寻找的行为。我希望第二个选择阻止,直到第一个完成他的交易。
      【解决方案13】:

      重新访问您的所有查询,也许您有一些查询在没有 ROWLOCK/FOR UPDATE 提示的情况下从您有 SELECT FOR UPDATE 的同一个表中选择。


      MSSQL 经常将这些行锁升级为页级锁(即使是表级锁,如果您正在查询的字段上没有索引),请参阅explanation。由于您要求更新,我可以假设您需要交易级别(例如财务、库存等)的稳健性。因此,该网站上的建议不适用于您的问题。这只是一个洞察为什么 MSSQL 升级锁


      如果您已经在使用 MSSQL 2005(及更高版本),它们是基于 MVCC 的,我认为使用 ROWLOCK/UPDLOCK 提示的行级锁定应该没有问题。但是,如果您已经在使用 MSSQL 2005 及更高版本,请尝试通过检查 WHERE 子句中的字段(如果它们有索引)来检查您的一些查询,这些查询查询您想要 FOR UPDATE 的同一个表是否升级锁。


      附言
      我正在使用 PostgreSQL,它也使用 MVCC 有 FOR UPDATE,我没有遇到同样的问题。锁升级是 MVCC 解决的问题,所以如果 MSSQL 2005 仍然使用在其字段上没有索引的 WHERE 子句升级表上的锁,我会感到惊讶。如果 MSSQL 2005 仍然存在这种情况(锁升级),请尝试检查 WHERE 子句中的字段是否有索引。

      免责声明:我上次使用的 MSSQL 是 2000 版。

      【讨论】:

      • 感谢文章的链接。我已经编写了一个 junit 测试,完全符合我的需要,我可以用几条语句重现丢失的锁或升级的锁。所以我可以保证不会有带和不带 ROWLOCK 的查询混合。
      【解决方案14】:

      根据this article,解决方法是使用 WITH(REPEATABLEREAD) 提示。

      【讨论】:

      • 好文章,谢谢。现在我确实对事情有了更好的理解。但不幸的是,提示 (repeatableread) 不会阻止对同一行的第二次选择。
      • tangens:你为什么认为它应该?
      • 因为这是我正在寻找的行为。
      • 是的,但是为什么您认为您正在使用的设置应该具有这种特定行为?
      • 我不这么认为。我正在寻找一种能够为我提供所需行为的解决方案。
      【解决方案15】:

      您不能同时进行快照隔离和阻塞读取。快照隔离的目的是防止阻塞读取。

      【讨论】:

        【解决方案16】:

        尝试使用:

        SELECT * FROM <tablename> WITH ROWLOCK XLOCK HOLDLOCK
        

        这应该使锁独占并在事务期间保持它。

        【讨论】:

        • (rowlock,xlock,holdlock) 也会阻塞其他行。
        • 有没有办法阻止它阻塞其他行?目前我面前没有 SQL Server 实例来测试它。也许是UPDLOCK XLOCK HOLDLOCK?
        • (updlock,xlock) 是不兼容的提示。
        • 然后试试UPDLOCK HOLDLOCK?
        • (updlock,holdlock) 也会阻止其他行。
        【解决方案17】:

        完整的答案可以深入研究 DBMS 的内部结构。这取决于查询引擎(执行由 SQL 优化器生成的查询计划)的运行方式。

        但是,一种可能的解释(至少适用于某些 DBMS 的某些版本 - 不一定适用于 MS SQL Server)是 ID 列上没有索引,因此任何尝试使用 'WHERE id = ? 进行查询的进程' 最终对表进行顺序扫描,并且该顺序扫描会命中您的进程应用的锁。如果 DBMS 默认应用页级锁定,您也可能会遇到问题;锁定一行会锁定整个页面以及该页面上的所有行。

        有一些方法可以揭穿这是麻烦的根源。查看查询计划;研究指标;尝试使用 ID 为 1000000 而不是 1 的 SELECT 并查看其他进程是否仍被阻止。

        【讨论】:

        • 所以依赖于MS SQL Server;也许它不会跳过索引上的锁。我建议在一个足够大的数据集上尝试“ID = ”测试,这样你就有多个页面在使用。您可能会看到不同之处;你可能不会。您可以尝试以“脏读”隔离级别运行“其他进程”;这应该可以让你通过阅读锁 - 但一般来说这不是一个很好的解决方案。
        • 所有进程都执行相同的读取操作:它们选择“他们的”记录并在操作结束时更新状态。
        • 那么,每个进程的ID值是一样的吗?或者他们每个人都使用不同的ID?我假设后者。问题在于 DBMS 是否允许足够不同的 ID 值绕过任何锁定。我的猜测是,如果 ID 值足够不同,您将获得过去的锁。但是,例如,如果不使用 ID 上的索引,则不会。你看过查询计划了吗?
        • 抱歉,我还不知道该怎么做。是的,每个进程使用不同的 ID。
        • 我查看了查询计划,是的,但没有发现任何特别之处。
        【解决方案18】:

        尝试(updlock,rowlock)

        【讨论】:

        • 是的,我尝试了行锁(没有与其他提示组合),但是同一行的并发 SELECT FOR UPDATE 不会阻塞。
        • 好的,我尝试了(updlock,rowlock),但是即使在访问另一行时,第二个 SELECT FOR UPDDATE 也会阻塞。
        • (xlock,rowlock) 也会阻塞其他行。
        • 为什么你认为它应该阻止?
        • 那是悲观的并发。如果你想要,那你为什么要指定乐观并发?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-25
        相关资源
        最近更新 更多