SELECT FOR UPDATE与SQL Server
我正在使用隔离级别为READ_COMMITTED
和READ_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隔离级别