85

我正在使用具有隔离级别的 Microsoft SQL Server 2005 数据库READ_COMMITTEDREAD_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:

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

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

我在寻找 | 没有 | 是的
4

18 回答 18

35

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

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

于 2009-09-30T08:37:49.363 回答
28

我有类似的问题,我只想锁定 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,因此这些行将被锁定。

于 2012-03-22T08:05:44.507 回答
9

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

于 2009-09-30T08:46:38.043 回答
6

也许使 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;
于 2009-10-07T03:48:08.393 回答
5

尝试(updlock,rowlock)

于 2009-09-27T14:59:47.210 回答
5

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

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

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

于 2009-09-27T15:00:42.260 回答
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

于 2009-10-03T21:32:15.513 回答
3

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

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

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

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

于 2011-06-14T03:27:12.103 回答
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
于 2010-02-18T21:51:20.497 回答
2

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

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

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

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

于 2010-02-19T00:01:54.327 回答
2

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

于 2011-02-11T00:26:55.497 回答
1

尝试使用:

SELECT * FROM <tablename> WITH ROWLOCK XLOCK HOLDLOCK

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

于 2009-09-27T15:38:52.497 回答
1

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

于 2009-09-30T09:00:37.413 回答
1

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


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


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


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

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

于 2009-09-30T09:37:31.300 回答
1

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

于 2009-11-08T21:10:01.430 回答
1

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

于 2011-06-04T20:48:26.513 回答
0

你试过READPAST吗?

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

于 2009-10-02T21:59:18.993 回答
0

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

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

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

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

于 2011-10-23T17:36:20.207 回答