SELECT FOR UPDATE与SQL Server

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

现在我想用:

 SELECT * FROM <tablename> FOR UPDATE 

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

我试过了:

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

…但是即使select“1”以外的其他连接也会阻止其他连接。

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

编辑2009-10-03:

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

 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:

为了更好的概述,我已经写下了下表中的所有尝试过的解决scheme:

机制| select不同的行块| select相同的行块
 ----------------------- + -------------------------- ------ + --------------------------
 ROWLOCK | 没有| 没有
 updlock,rowlock | 是| 是
 xlock,rowlock | 是| 是
 repeatableread | 没有| 没有
 DBCC TRACEON(1211,-1)| 是| 是
 rowlock,xlock,holdlock | 是| 是
 updlock,holdlock | 是| 是
 UPDLOCK,READPAST | 没有| 没有

我正在寻找| 没有| 是

最近我有一个死锁问题,因为SQL Serverlocking更多然后必要(页面)。 你真的不能做任何反对的事情。 现在我们正在捕捉死锁例外…我希望我有Oracle。

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

我有一个类似的问题,我想只locking1行。 据我所知,使用UPDLOCK选项,SQLSERVERlocking所有需要读取的行以获取行。 因此,如果您没有定义索引来直接访问该行,则所有先前的行都将被locking。 在你的例子中:

假设你有一个名为TBL的id表。 你想lockingid=10的行。 您需要为字段ID(或您select的任何其他字段)定义索引:

 CREATE INDEX TBLINDEX ON TBL ( id ) 

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

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

如果不使用INDEX(TBLINDEX)选项,则SQLSERVER需要从表的开头读取所有行,以查找id=10行,以便locking这些行。

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

试试(updlock,rowlock)

完整的答案可以深入到DBMS的内部。 这取决于查询引擎(执行由SQL优化器生成的查询计划)如何操作。

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

有一些方法可以将这个问题作为麻烦来源。 查看查询计划; 研究指标; 尝试您的selectID为1000000而不是1,看看其他进程是否仍然被阻止。

也许使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; 

好吧,默认情况下,单个selectwil使用“读取已提交”事务隔离,它将locking并因此停止写入该集合。 您可以使用更改事务隔离级别

 Set Transaction Isolation Level { Read Uncommitted | Read Committed | Repeatable Read | Serializable } Begin Tran Select ... Commit Tran 

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

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

您可以通过设置“跟踪标志”1211tclosures此升级,请参阅BOL了解更多信息

我假设你不希望任何其他会话能够读取该行,而这个特定的查询正在运行…

使用WITH(XLOCK,READPAST)locking提示时,在交易中包装您的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 

创build一个伪更新来强制执行该行。

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

如果这不是locking你的行,上帝知道会有什么。

在这个“ UPDATE ”之后,你可以做你的SELECT (ROWLOCK)和后续的更新。

尝试使用:

 SELECT * FROM <tablename> WITH ROWLOCK XLOCK HOLDLOCK 

这应该使锁在排除交易持续时间。

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

重新访问所有的查询,也许你有一些查询select没有ROWLOCK / FOR UPDATE提示从同一张表你有SELECT FOR UPDATE。

MSSQL通常会将这些行锁升级为页级锁(即使是表级锁,如果您在查询的字段上没有索引),请参阅此解释 。 既然你要求更新,我可以假设你需要交易层面(如财务,库存等)的稳健性。 因此,该网站上的build议不适用于您的问题。 这只是一个为什么MSSQL 升级锁的洞察力。

如果你已经在使用MSSQL 2005(和以上),它们是基于MVCC的,我想你应该没有使用ROWLOCK / UPDLOCK提示进行行级locking的问题。 但是,如果您已经在使用MSSQL 2005或更高版本,请尝试检查一些查询,这些查询查询您希望FOR UPDATE的同一个表,如果它们通过检查其WHERE子句中的字段(如果它们具有索引)来升级locking。

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

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

您必须在提交时处理exception并重复交易。

问题 – 这种情况是否被certificate是锁升级的结果(即,如果跟踪locking升级事件的分析器,是否会发生阻塞)? 如果是这样,通过在实例级别启用跟踪标志来防止锁升级,有一个完整的解释和一个(相当极端的)解决方法。 请参阅http://support.microsoft.com/kb/323630跟踪标志1211

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

如果你故意locking一行并长时间保持locking,那么使用内部locking机制进行事务并不是最好的方法(至less在SQL Server中)。 SQL Server中的所有优化都面向短交易 – 进入,更新,退出。 这首先是锁升级的原因。

因此,如果意图是长时间“检查”一行,而不是事务性locking,最好使用带值的列和普通的ol update语句将行标记为locking或不locking。

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

我用完全不同的方式解决了rowlock问题。 我意识到,SQL服务器无法以令人满意的方式pipe理这样一个锁。 我select从编程的angular度来解决这个问题,通过使用互斥体… waitForLock … releaseLock …

你有没有试过READPAST?

在处理表格时,我一起使用了UPDLOCK和READPAST。

如何尝试在这一行上做一个简单的更新(没有真正改变任何数据)? 之后,你可以继续像在被选中更新的行。

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

编辑 :你应该把它包装在一个交易当然

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