如果两个并发DML语句修改相同的数据并使用相同的访问方法,是否可能出现死锁?
根据我的测试,以及我对Oracle如何工作的猜测,答案是否定的。
但我想百分之百肯定。我正在寻找一个官方的消息来源,它说死锁不可能以这种方式发生,或者一个测试用例来证明死锁可以以这种方式发生。
问这个问题的另一种方法是:如果使用相同的访问方法,Oracle是否总是以相同的顺序返回结果?(并且在运行之间没有数据更改。)
例如,如果查询使用完整的表扫描并以4/3/2/1的顺序返回行,那么它是否总是按照该顺序返回行?如果索引范围扫描按1/2/3/4的顺序返回行,它是否总是按照该顺序返回行?不管实际的顺序是什么,只是顺序是确定性的。
(并行可能会给这个问题增加一些复杂性。根据许多因素,声明的总体顺序将有所不同。但对于锁定,我认为只有在每个平行会议内的顺序是重要的。同样,我的测试表明,顺序是确定性的,不会导致死锁。)
更新
我原来的问题有点笼统。我最感兴趣的是,是否有可能同时在两个不同的会话中运行类似update table_without_index set a = -1的程序,并获得一个死锁?(我问的是一次更新,而不是一系列更新。)
首先,让我证明,完全相同的说法会造成僵局。
创建一个表、索引和一些数据:
为了简单起见,我只更新同一列。在现实世界中,可能会有不同的列,但我认为这不会改变任何事情。
请注意,我使用pctfree 0创建了表,更新后的值将占用更多的空间,因此将有大量的行迁移。(这是对@Tony的回答的回应,尽管我担心我的测试可能过于简单。另外,我认为我们不需要担心在更新之间插入行;只有一个更新会看到新行,这样就不会导致死锁。除非新行也转移了一堆其他东西。)
drop table deadlock_test purge;
create table deadlock_test(a number) pctfree 0;
create index deadlock_test_index on deadlock_test(a);
insert into deadlock_test select 2 from dual connect by level <= 10000;
insert into deadlock_test select 1 from dual connect by level <= 10000;
commit;在会话1中运行此块:
begin
while true loop
update deadlock_test set a = -99999999999999999999 where a > 0;
rollback;
end loop;
end;
/在会话2中运行此块:
--First, influence the optimizer so it will choose an index range scan.
--This is not gaurenteed to work for every environment. You may need to
--change other settings for Oracle to choose the index over the table scan.
alter session set optimizer_index_cost_adj = 1;
begin
while true loop
update deadlock_test set a = -99999999999999999999 where a > 0;
rollback;
end loop;
end;
/几秒钟后,其中一个会话抛出ORA-00060: deadlock detected while waiting for resource。这是因为相同的查询在每个会话中以不同的顺序锁定行。
排除了上述情况,会出现死锁吗?
上述说明了执行计划中的更改会导致死锁。但是,即使执行计划保持不变,死锁也会发生吗?
据我所知,如果您移除optimizer_index_cost_adj或任何其他更改计划的内容,代码将不会导致死锁。(我运行代码已经有一段时间了,没有错误。)
我问这个问题是因为我正在研究的一个系统偶尔会发生这种情况。它还没有失败,但我们想知道它是否真的安全,或者我们是否需要在更新周围添加额外的锁定?
能否构建一个测试用例,其中一个并发运行并使用相同计划的update语句会导致死锁?。
发布于 2012-02-09 18:19:24
从您的角度来看,只有在查询中包含ORDER,"order“才是决定性的。从服务器的角度来看,它是否是确定性的,这是一个实现细节,不需要依赖。
至于锁定,两个相同的DML语句可以彼此阻塞(但不是死锁)。例如:
CREATE TABLE THE_TABLE (
ID INT PRIMARY KEY
);交易A:
INSERT INTO THE_TABLE VALUES(1);交易B:
INSERT INTO THE_TABLE VALUES(1);此时,事务B是停止的,直到事务A提交或回滚。如果A提交,则B由于主键违反而失败。如果A回滚,B就成功了。
可以为更新和删除构造类似的示例。
重要的一点是,阻塞将不取决于执行计划--无论Oracle如何选择优化查询,始终具有相同的阻塞行为。您可能希望阅读有关DML操作中的自动锁的更多信息。
至于dead-locks,可以使用多个语句来实现。例如:
A: INSERT INTO THE_TABLE VALUES(1);
B: INSERT INTO THE_TABLE VALUES(2);
A: INSERT INTO THE_TABLE VALUES(2);
B: INSERT INTO THE_TABLE VALUES(1); -- SQL Error: ORA-00060: deadlock detected while waiting for resource或者,可能使用语句以不同的顺序修改多个行和一些非常不吉利的时间(有人能证实这一点吗?)
-更新
在回答更新您的问题时,让我做一个一般性的观察:如果执行锁对象的并发线程在一致的order中,死锁是不可能的。这对于任何类型的锁定都是正确的,无论是普通多线程程序中的互斥(例如,参见赫伯·萨特的锁等级思想)还是数据库。一旦您以“翻转”任何两个锁的方式更改顺序,就会引入死锁的可能性。
在不扫描索引的情况下,您将按一个顺序更新(和锁定)行,并在另一个顺序中使用索引更新行。因此,在您的情况下,可能会发生这样的情况:
alter session set optimizer_index_cost_adj = 1;,您将看到)。虽然我不会依赖具有保证顺序的全表扫描--这可能正是当前Oracle在这些特定情况下的工作方式,而且将来的Oracle或不同的环境可能会产生不同的行为。
因此,指数的存在是偶然的-真正的问题是排序。当然,更新中的排序可能会受到索引的影响,但是如果我们能够以另一种方式影响排序,我们就会得到类似的结果。
由于UPDATE没有ORDER,所以不能真正保证只按UPDATE锁定的顺序。但是,如果将锁定与更新分开,则可以保证锁定顺序:
SELECT ... ORDER BY ... FOR UPDATE;虽然原始代码在我的Oracle 10环境中导致死锁,但以下代码没有:
会话1:
declare
cursor cur is select * from deadlock_test where a > 0 order by a for update;
begin
while true loop
for locked_row in cur loop
update deadlock_test set a = -99999999999999999999 where current of cur;
end loop;
rollback;
end loop;
end;
/会话2:
alter session set optimizer_index_cost_adj = 1;
declare
cursor cur is select * from deadlock_test where a > 0 order by a for update;
begin
while true loop
for locked_row in cur loop
update deadlock_test set a = -99999999999999999999 where current of cur;
end loop;
rollback;
end loop;
end;
/发布于 2012-02-09 18:12:03
返回行的顺序不是确定性的。行在更新后有可能“迁移”到不同的块,在这种情况下,行将在全表扫描的结果中出现在不同的位置。或者(可能更有可能)在两个现有行之间插入一个新行。
https://stackoverflow.com/questions/9216150
复制相似问题