使用SQL Server选择更新


78

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

现在我要使用:

SELECT * FROM <tablename> FOR UPDATE

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

我试过了:

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

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

SELECT FOR UPDATE以Oracle,DB2,MySql所知,哪个是正确的提示?

编辑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:

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

机制| 在不同的行块上选择| 在同一行块上选择
----------------------- + -------------------------- ------ + --------------------------
ROWLOCK | 没有 没有
上锁,行锁| 是的 是
xlock,rowlock | 是的 是
重复阅读| 没有 没有
DBCC TRACEON(1211,-1)| 是的 是
行锁,xlock,holdlock | 是的 是
上锁,保持锁| 是的 是
UPDLOCK,READPAST | 没有 没有

我在寻找| 没有 是

我不仅在寻找优化器提示。另一个可能的解决方案是更改隔离级别,全局数据库属性,...一切皆有可能(但要使用其他数据库)。
tangens

4
您想要做什么需要这种锁定。通常最好使用适当的查询而不是服务器的“功能”来解决
TFD

2
您能否提供您正在使用的查询以及表的DDL,包括任何键和索引。
RBarryYoung

您确定其他查询不是未提交未提交的事务隔离吗?
内森·费格

1
作为一种解决方法,您可以尝试首先在此行上进行简单更新(而无需真正更改任何数据)。之后,您可以继续进行选择中的行。
弗拉基米尔

Answers:


34

最近,我遇到了死锁问题,因为Sql Server锁定的锁超过了必需的锁(页)。您真的不能对此采取任何措施。现在,我们正在捕获死锁异常……我希望我改用Oracle。

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


恐怕这是真的。我发现没有办法获得有效的“选择更新”。我现在的解决方案是放弃“ SELECT FOR UPDATE”并执行一个简单的,非阻塞的“ SELECT”,并使用更新计数器(“ UPDATE WHERE id =?和updateCount =?”)检查并发的“ UPDATES”。
tangens

1
SQL 2008提供了两个新的乐观隔离级别,类似于Oracle提供的隔离级别
Chris Bednarski,2010年

@ChrisBednarski我在SQL Server 2008 R2中使用了WITH(ROWLOCK),但它仍然锁定了多行!您能提供一些解释吗
2012年

2
@bjan:查找快照隔离。这里的一些信息msdn.microsoft.com/en-us/library/tcbchxcb(v=vs.80).aspx
Chris Bednarski 2012年

1
@ChrisBednarski感谢您的链接,这两个命令使白天和黑夜变得不同:ALTER DATABASE MyDatabase SET ALLOW_SNAPSHOT_ISOLATION ON; ALTER DATABASE MyDatabase设置为READ_COMMITTED_SNAPSHOT ON;另请参阅MichaelBuen对这个问题的回答。
拉斯洛·范登霍克

22

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

假设您有一个带有id字段的名为TBL的表。您想使用锁定行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的要求
Shmil The Cat 2014年

-1我有一个聚集索引,并且在提示中包含聚集索引没有区别。一旦我意识到我错误地触摸了sp中锁定SELECT语句之后的其他记录(丢失了原始select中存在的id的条件),则updlock + rowlock + holdlock发挥了很大的作用。
MoonStom 2015年


5

试试(上锁,行锁)


是的,我尝试了行锁(不与其他提示结合使用),但是同一行的并发SELECT FOR UPDATE不会阻塞。
tangens 2009年

好的,我尝试过(updlock,rowlock),但是即使访问另一行,第二个SELECT FOR UPDDATE块也会阻塞。
tangens

(xlock,rowlock)也会阻塞其他行。
tangens

您为什么认为它应该阻止?
RBarryYoung

1
那是悲观的并发。如果您愿意,为什么要指定乐观并发?
RBarryYoung

5

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

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

您可以通过一些方法将其视为麻烦的根源。查看查询计划;研究指标;请尝试使用ID为1000000而不是1的SELECT来查看是否仍然阻止了其他进程。


因此,它取决于MS SQL Server。也许它不会跳过索引上的锁。我建议对足够多的数据集(正在使用多个页面)尝试“ ID = <大数>”测试。您可能会发现有所不同;你可能不会。您可以尝试以“脏读”隔离级别运行“其他进程”。这应该使您摆脱阅读的束缚-但这通常不是一个很好的解决方案。
乔纳森·莱夫勒

所有进程都读取相同的内容:它们选择“其”记录并在操作结束时更新状态。
tangens

1
那么,每个进程的ID值都相同吗?还是他们各自使用不同的ID?我认为是后者。问题是DBMS是否将允许足够不同的ID值绕过任何锁定。我的猜测是,如果ID值足够不同,您将获得过去的锁。但是,例如,如果不使用ID上的索引,则不会。您看过查询计划了吗?
乔纳森·莱夫勒

抱歉,我还无法弄清楚该怎么做。是的,每个进程使用不同的ID。
tangens

我看过查询计划,但是没有发现什么特别之处。
tangens

5

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

ALTER DATABASE yourDbNameHere SET READ_COMMITTED_SNAPSHOT ON;

[编辑:10月14日]

读完这篇文章之后:Oracle中比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中的数据库属性对话框,系统将提示您断开所有客户端的连接,并且无需重新启动。
拉斯洛·范登霍克

3

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

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


非常感谢您,这听起来可能是解决方案。我星期一再试试。
tangens

我尝试过,但是没有用。访问另一个行时,第二个SELECT仍然阻塞。
tangens

3

创建一个伪造的更新以强制执行行锁。

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

如果那没有锁住你的行,上帝知道会怎样。

之后,UPDATE您就可以SELECT (ROWLOCK)进行更新了。


我想知道这是否比替代方法更糟糕,替代方法只是使用xlock提示进行选择。行锁不一定是可靠的,因为SQL Server实际上并不锁定行,而是锁定行的6字节哈希,因此有可能与表中的大量记录发生冲突。此外,由于行锁是“提示”,因此它不能保证不会发生锁升级,尽管您可以通过禁用表上的锁升级来最大程度地降低这种风险。
Triynko

2

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

在使用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

2

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

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

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

因此,如果打算长时间“检出”某行,而不是事务锁定,则最好使用具有值的列和简单的ol'update语句将行标记为已锁定或未锁定。


同意,尽管我宁愿采用一种乐观的锁定方法来创建行版本,就像在ORM的/持久层(例如Hibernate)中所做的那样,请参阅:codippa.com/…假定在受控环境中,更新通过特定的应用程序进行,或多个应用程序采用相同的方法。本文介绍了大多数情况下,包括SQL Server的具体“rowversion”数据类型:simple-talk.com/sql/t-sql-programming/...
卡马尔

2

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


不幸的是,当涉及死锁问题时,应用程序锁并不总是有用。无论使用sp_getapplock锁定什么,当您实际开始修改数据库记录时,都必须应对以下事实:引擎开始锁定行,并且由于页或表锁升级可能会碰撞而无意中锁定了不相关的行与并发交易。最好禁用锁升级并指定排他的行锁提示,并按定义的锁定顺序锁定行,以尽可能避免死锁。
Triynko

我宁愿采用一种乐观的锁定方法来创建行版本,因为它是在ORM的/持久层(例如Hibernate)中完成的,请参见:codippa.com/…这假设一个受控的环境中,更新通过特定的应用程序进行,或者通过多个应用程序采用相同的方法。本文介绍了大多数情况下,包括SQL Server的具体“rowversion”的数据类型和自己sp_getapplock建议:simple-talk.com/sql/t-sql-programming/...
卡迈勒

1

尝试使用:

SELECT * FROM <tablename> WITH ROWLOCK XLOCK HOLDLOCK

这应该使锁排他并在事务期间保持它。


(rowlock,xlock,holdlock)也会阻塞其他行。
tangens

有没有办法阻止它阻塞其他行?目前我还没有SQL Server实例可以对其进行测试。也许UPDLOCK XLOCK HOLDLOCK?
RMorrisey

(updlock,xlock)是不兼容的提示。
tangens 2009年

然后尝试UPDLOCK HOLDLOCK吗?
RMorrisey

(updlock,holdlock)也会阻塞其他行。
tangens

1

根据本文,解决方案是使用WITH(REPEATABLEREAD)提示。


不错的文章,谢谢。现在我的确了解得更多了。但不幸的是,提示(可重复读取)不会阻止同一行的第二次选择。
tangens 2009年

坦根斯:为什么您认为应该这样做?
RBarryYoung

因为这就是我要寻找的行为。
tangens

是的,但是为什么您认为您使用的设置应该具有该特定行为?
RBarryYoung

6
我不这样认为。我正在寻找一种可以提供所需性能的解决方案。
tangens

1

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


MSSQL通常将这些行锁升级为页面级锁(甚至表级锁,如果您在查询的字段上没有索引),请参见以下说明。由于您要求进行更新,因此我可以假设您需要事务级别(例如财务,库存等)的鲁棒性。因此,该网站上的建议不适用于您的问题。这只是MSSQL升级锁的原因之一


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


聚苯乙烯
我使用PostgreSQL,它也使用MVCC有更新,我没有遇到同样的问题。锁升级是MVCC解决的问题,因此,如果MSSQL 2005仍使用不包含其字段索引的WHERE子句升级表上的锁,我将感到惊讶。如果对于MSSQL 2005仍然(锁定升级),请尝试检查WHERE子句上的字段是否具有索引。

免责声明:我最后一次使用MSSQL仅是2000版。


感谢您的文章链接。我已经编写了一个junit测试来完成我所需要的,并且仅需少量语句就可以重现丢失的锁或升级的锁。因此,我可以保证在有和没有ROWLOCK的情况下都不会混合查询。
tangens 2009年

1

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


1

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


4
只要仅一台服务器需要更新数据库,该方法就起作用。
Andrew Swan 2013年

0

您是否尝试过READPAST?

将表当作队列使用时,我一起使用了UPDLOCK和READPAST。


我尝试过,但是第二次选择没有找到记录。那不是我要寻找的行为。我要阻止第二个选择,直到第一个选择完成他的交易。
tangens

0

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

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

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

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


该解决方案与Feu 2011年6月14日之前提供的解决方案相同。
barrypicker 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.