使用SQL Server选择更新

时间:2009-09-27 14:50:12

标签: sql sql-server sql-server-2005 tsql read-committed-snapshot

我正在使用隔离级别为READ_COMMITTEDREAD_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

18 个答案:

答案 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隔离级别