我正在使用隔离级别为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
...但即使选择“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:
为了更好地概述,我在下表中写下了所有尝试过的解决方案:
mechanism | SELECT on different row blocks | SELECT on same row blocks -----------------------+--------------------------------+-------------------------- ROWLOCK | no | no updlock, rowlock | yes | yes xlock,rowlock | yes | yes repeatableread | no | no DBCC TRACEON (1211,-1) | yes | yes rowlock,xlock,holdlock | yes | yes updlock,holdlock | yes | yes UPDLOCK,READPAST | no | no I'm looking for | no | yes
答案 0 :(得分:33)
最近我有一个deadlock problem,因为Sql Server会锁定更多必要的(页面)。你无法真正做任何反对它。现在我们正在捕捉死锁异常...而且我希望我改为使用Oracle。
编辑: 我们同时使用快照隔离,它解决了许多但不是所有问题。遗憾的是,为了能够使用快照隔离,数据库服务器必须允许它,这可能会在客户站点上造成不必要的问题。现在我们不仅捕获死锁异常(当然仍然可以发生),而且还捕获快照并发问题以从后台进程重复事务(用户不能重复)。但这仍然比以前表现得更好。
答案 1 :(得分:17)
我有类似的问题,我想只锁定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
的行,因此这些行将被锁定。
答案 2 :(得分:7)
您无法同时进行快照隔离和阻止读取。快照隔离的目的是阻止阻止读取。
答案 3 :(得分:5)
或许使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;
答案 4 :(得分:5)
尝试(updlock,rowlock)
答案 5 :(得分:5)
完整的答案可以深入研究DBMS的内部结构。它取决于查询引擎(执行SQL优化器生成的查询计划)的运行方式。
但是,一种可能的解释(适用于某些DBMS的至少某些版本 - 不一定适用于MS SQL Server)是ID列上没有索引,因此任何尝试使用“{{1”进行查询的进程最终对表执行顺序扫描,顺序扫描会触发您的进程应用的锁。如果DBMS默认应用页面级锁定,也会遇到问题;锁定一行会锁定整个页面以及该页面上的所有行。
有些方法可以将此作为麻烦的来源揭穿。查看查询计划;研究指标;尝试使用ID为1000000而不是1的SELECT,并查看其他进程是否仍然被阻止。
答案 6 :(得分:3)
好的,默认情况下,单个select wil使用“Read Committed”事务隔离,它会锁定并因此停止对该集的写入。您可以使用
更改事务隔离级别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
答案 7 :(得分: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
答案 8 :(得分:2)
应用程序锁是使用自定义粒度滚动自己的锁定的一种方法,同时避免“有用”的锁升级。请参阅sp_getapplock。
答案 9 :(得分:2)
创建虚假更新以强制执行行锁。
UPDATE <tablename> (ROWLOCK) SET <somecolumn> = <somecolumn> WHERE id=1
如果那不是锁定你的行,上帝知道会发生什么。
在此“UPDATE
”之后,您可以执行SELECT (ROWLOCK)
及后续更新。
答案 10 :(得分:1)
根据this article,解决方案是使用WITH(REPEATABLEREAD)提示。
答案 11 :(得分:1)
重新访问您的所有查询,也许您有一些查询在没有ROWLOCK / FOR UPDATE提示的情况下从您选择更新的同一个表中进行选择。
MSSQL通常会将这些行锁升级为页级锁(即使是表级锁,如果您在查询的字段上没有索引),请参阅此explanation。既然你要求FOR UPDATE,我可以假设你需要transacion级别(例如财务,库存等)的稳健性。因此,该网站上的建议不适用于您的问题。这只是为什么MSSQL 升级锁。
如果您已经在使用MSSQL 2005(及以上版本),它们是基于MVCC的,我认为使用ROWLOCK / UPDLOCK提示您应该没有行级锁定的问题。但是,如果您已经在使用MSSQL 2005及更高版本,请尝试检查一些查询,如果它们通过检查WHERE子句上的字段(如果它们具有索引)来升级锁定,则查询要更新的FOR FORDATE的同一个表。
附:
我正在使用PostgreSQL,它也使用MVCC有FOR UPDATE,我没有遇到同样的问题。锁定升级是MVCC解决的问题,所以如果MSSQL 2005仍然使用在其字段上没有索引的WHERE子句升级表上的锁,我会感到惊讶。如果MSSQL 2005仍然存在这种情况(锁定升级),请尝试检查WHERE子句中的字段(如果它们具有索引)。
免责声明:我上次使用MSSQL仅限2000版。
答案 12 :(得分:1)
尝试使用:
SELECT * FROM <tablename> WITH ROWLOCK XLOCK HOLDLOCK
这应该使锁独占并在交易期间持有它。
答案 13 :(得分:1)
您必须在提交时处理异常并重复该事务。
答案 14 :(得分:1)
问题 - 这种情况被证明是锁升级的结果(即如果您使用分析器跟踪锁升级事件,那肯定是导致阻塞的原因)?如果是这样,通过在实例级别启用跟踪标志来防止锁定升级,可以获得完整的解释和(相当极端的)解决方法。见http://support.microsoft.com/kb/323630 trace flag 1211
但是,这可能会产生意想不到的副作用。
如果您故意锁定一行并将其长时间锁定,那么使用内部锁定机制进行事务处理并不是最好的方法(至少在SQL Server中)。 SQL Server中的所有优化都面向短交易 - 进入,进行更新,退出。这就是锁定升级的原因。
因此,如果意图是长时间“检出”一行,而不是事务锁定,最好使用带有值的列和一个普通的'update语句来将行标记为已锁定或不被锁定。
答案 15 :(得分:1)
我以完全不同的方式解决了行锁问题。我意识到sql server无法以令人满意的方式管理这样的锁。我选择通过使用互斥锁以编程方式来解决这个问题... waitForLock ... releaseLock ...
答案 16 :(得分:0)
你试过READPAST吗?
在处理像队列这样的表时,我一直使用UPDLOCK和READPAST。
答案 17 :(得分:0)
如何首先尝试对此行进行简单更新(而不是真正更改任何数据)?之后,您可以继续选择要更新的行。
UPDATE dbo.Customer SET FieldForLock = FieldForLock WHERE CustomerID = @CustomerID
/* do whatever you want */
编辑:您应该将其包装在当然的事务中
编辑2 :另一种解决方案是使用SERIALIZABLE隔离级别