使用 SQL Server 选择进行更新

发布于 2024-08-06 20:08:42 字数 1761 浏览 7 评论 0 原文

我使用的 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 也是如此。

对于 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

I'm using a Microsoft SQL Server 2005 database with isolation level READ_COMMITTED and READ_COMMITTED_SNAPSHOT=ON.

Now I want to use:

SELECT * FROM <tablename> FOR UPDATE

...so that other database connections block when trying to access the same row "FOR UPDATE".

I tried:

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

...but this blocks all other connections even for selecting an id other than "1".

Which is the correct hint to do a SELECT FOR UPDATE as known for Oracle, DB2, MySql?

EDIT 2009-10-03:

These are the statements to create the table and the index:

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 )

A lot of parallel processes do this SELECT:

SELECT * FROM example o WITH (updlock) WHERE o.TransactionId = ?

EDIT 2009-10-05:

For a better overview I've written down all tried solutions in the following table:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(18

∞觅青森が 2024-08-13 20:08:42

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

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

Recently I had a deadlock problem because Sql Server locks more then necessary (page). You can't really do anything against it. Now we are catching deadlock exceptions... and I wish I had Oracle instead.

Edit:
We are using snapshot isolation meanwhile, which solves many, but not all of the problems. Unfortunately, to be able to use snapshot isolation it must be allowed by the database server, which may cause unnecessary problems at customers site. Now we are not only catching deadlock exceptions (which still can occur, of course) but also snapshot concurrency problems to repeat transactions from background processes (which cannot be repeated by the user). But this still performs much better than before.

晨与橙与城 2024-08-13 20:08:42

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

I have a similar problem, I want to lock only 1 row.
As far as I know, with UPDLOCK option, SQLSERVER locks all the rows that it needs to read in order to get the row. So, if you don't define a index to direct access to the row, all the preceded rows will be locked.
In your example:

Asume that you have a table named TBL with an id field.
You want to lock the row with id=10.
You need to define a index for the field id (or any other fields that are involved in you select):

CREATE INDEX TBLINDEX ON TBL ( id )

And then, your query to lock ONLY the rows that you read is:

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

If you don't use the INDEX(TBLINDEX) option, SQLSERVER needs to read all rows from the beginning of the table to find your row with id=10, so those rows will be locked.

童话里做英雄 2024-08-13 20:08:42

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

You cannot have snapshot isolation and blocking reads at the same time. The purpose of snapshot isolation is to prevent blocking reads.

永言不败 2024-08-13 20:08:42

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

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

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

The full answer could delve into the internals of the DBMS. It depends on how the query engine (which executes the query plan generated by the SQL optimizer) operates.

However, one possible explanation (applicable to at least some versions of some DBMS - not necessarily to MS SQL Server) is that there is no index on the ID column, so any process trying to work a query with 'WHERE id = ?' in it ends up doing a sequential scan of the table, and that sequential scan hits the lock which your process applied. You can also run into problems if the DBMS applies page-level locking by default; locking one row locks the entire page and all the rows on that page.

There are some ways you could debunk this as the source of trouble. Look at the query plan; study the indexes; try your SELECT with ID of 1000000 instead of 1 and see whether other processes are still blocked.

り繁华旳梦境 2024-08-13 20:08:42

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

perhaps making mvcc permanent could solve it (as opposed to specific batch only: SET TRANSACTION ISOLATION LEVEL SNAPSHOT):

ALTER DATABASE yourDbNameHere SET READ_COMMITTED_SNAPSHOT ON;

[EDIT: October 14]

After reading this: Better concurrency in Oracle than SQL Server? and this: http://msdn.microsoft.com/en-us/library/ms175095.aspx

When the READ_COMMITTED_SNAPSHOT
database option is set ON, the
mechanisms used to support the option
are activated immediately. When
setting the READ_COMMITTED_SNAPSHOT
option, only the connection executing
the ALTER DATABASE command is allowed
in the database. There must be no
other open connection in the database
until ALTER DATABASE is complete. The
database does not have to be in
single-user mode.

i've come to conclusion that you need to set two flags in order to activate mssql's MVCC permanently on a given database:

ALTER DATABASE yourDbNameHere SET ALLOW_SNAPSHOT_ISOLATION ON;
ALTER DATABASE yourDbNameHere SET READ_COMMITTED_SNAPSHOT ON;
岁月蹉跎了容颜 2024-08-13 20:08:42

尝试(updlock、rowlock)

Try (updlock, rowlock)

筱武穆 2024-08-13 20:08:42

好的,默认情况下,单个选择将使用“已提交读”事务隔离,该隔离会锁定并因此停止对该集的写入。您可以使用以下命令更改事务隔离级别:

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 了解更多信息

OK, a single select wil by default use "Read Committed" transaction isolation which locks and therefore stops writes to that set. You can change the transaction isolation level with

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

These are explained in detail in SQL Server BOL

Your next problem is that by default SQL Server 2K5 will escalate the locks if you have more than ~2500 locks or use more than 40% of 'normal' memory in the lock transaction. The escalation goes to page, then table lock

You can switch this escalation off by setting "trace flag" 1211t, see BOL for more information

梅窗月明清似水 2024-08-13 20:08:42

我假设您不希望任何其他会话能够在该特定查询运行时读取该行...

在使用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

I'm assuming you don't want any other session to be able to read the row while this specific query is running...

Wrapping your SELECT in a transaction while using WITH (XLOCK,READPAST) locking hint will get the results you want. Just make sure those other concurrent reads are NOT using WITH (NOLOCK). READPAST allows other sessions to perform the same SELECT but on other rows.

BEGIN TRAN
  SELECT *
  FROM <tablename> WITH (XLOCK,READPAST) 
  WHERE RowId = @SomeId

  -- Do SOMETHING

  UPDATE <tablename>
  SET <column>=@somevalue
  WHERE RowId=@SomeId
COMMIT
凉宸 2024-08-13 20:08:42

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

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

如果这不能锁定你的行,天知道什么会锁定你的行。

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

Create a fake update to enforce the rowlock.

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

If that's not locking your row, god knows what will.

After this "UPDATE" you can do your SELECT (ROWLOCK) and subsequent updates.

蓝色星空 2024-08-13 20:08:42

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

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

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

因此,如果目的是长时间“签出”一行,最好使用包含值的列和简单的更新语句来将行标记为锁定或未锁定,而不是事务锁定。

Question - is this case proven to be the result of lock escalation (i.e. if you trace with profiler for lock escalation events, is that definitely what is happening to cause the blocking)? If so, there is a full explanation and a (rather extreme) workaround by enabling a trace flag at the instance level to prevent lock escalation. See http://support.microsoft.com/kb/323630 trace flag 1211

But, that will likely have unintended side effects.

If you are deliberately locking a row and keeping it locked for an extended period, then using the internal locking mechanism for transactions isn't the best method (in SQL Server at least). All the optimization in SQL Server is geared toward short transactions - get in, make an update, get out. That's the reason for lock escalation in the first place.

So if the intent is to "check out" a row for a prolonged period, instead of transactional locking it's best to use a column with values and a plain ol' update statement to flag the rows as locked or not.

恏ㄋ傷疤忘ㄋ疼 2024-08-13 20:08:42

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

Application locks are one way to roll your own locking with custom granularity while avoiding "helpful" lock escalation. See sp_getapplock.

流绪微梦 2024-08-13 20:08:42

尝试使用:

SELECT * FROM <tablename> WITH ROWLOCK XLOCK HOLDLOCK

这应该使锁具有独占性,并在事务持续时间内保持它。

Try using:

SELECT * FROM <tablename> WITH ROWLOCK XLOCK HOLDLOCK

This should make the lock exclusive and hold it for the duration of the transaction.

北斗星光 2024-08-13 20:08:42

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

According to this article, the solution is to use the WITH(REPEATABLEREAD) hint.

我们的影子 2024-08-13 20:08:42

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

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

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

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

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

Revisit all your queries, maybe you have some query that select without ROWLOCK/FOR UPDATE hint from the same table you have SELECT FOR UPDATE.

MSSQL often escalates those row locks to page-level locks (even table-level locks, if you don't have index on field you are querying), see this explanation. Since you ask for FOR UPDATE, i could assume that you need transacion-level(e.g. financial, inventory, etc) robustness. So the advice on that site is not applicable to your problem. It's just an insight why MSSQL escalates locks.

If you are already using MSSQL 2005(and up), they are MVCC-based, i think you should have no problem with row-level lock using ROWLOCK/UPDLOCK hint. But if you are already using MSSQL 2005 and up, try to check some of your queries which query the same table you want to FOR UPDATE if they escalate locks by checking the fields on their WHERE clause if they have index.

P.S.
I'm using PostgreSQL, it also uses MVCC have FOR UPDATE, i don't encounter same problem. Lock escalations is what MVCC solves, so i would be surprised if MSSQL 2005 still escalate locks on table with WHERE clauses that doesn't have index on its fields. If that(lock escalation) is still the case for MSSQL 2005, try to check the fields on WHERE clauses if they have index.

Disclaimer: my last use of MSSQL is version 2000 only.

如果没有 2024-08-13 20:08:42

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

You have to deal with the exception at commit time and repeat the transaction.

泛泛之交 2024-08-13 20:08:42

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

I solved the rowlock problem in a completely different way. I realized that sql server was not able to manage such a lock in a satisfying way. I choosed to solve this from a programatically point of view by the use of a mutex... waitForLock... releaseLock...

情归归情 2024-08-13 20:08:42

你试过READPAST吗?

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

Have you tried READPAST?

I've used UPDLOCK and READPAST together when treating a table like a queue.

墨小墨 2024-08-13 20:08:42

首先尝试对此行进行简单的更新(而不真正更改任何数据)怎么样?之后,您可以继续处理该行,就像选择更新一样。

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

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

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

How about trying to do a simple update on this row first (without really changing any data)? After that you can proceed with the row like in was selected for update.

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

Edit: you should wrap it in a transaction of course

Edit 2: another solution is to use SERIALIZABLE isolation level

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文