如果两个并发DML语句修改相同的数据并使用相同的访问方法,是否可能发生死锁?
基于我的测试,以及我对Oracle如何工作的猜测,答案是否定的。
但我想100%肯定。我正在寻找一个官方消息来源,说死锁不会发生这种情况,或者是一个测试案例,证明死锁可以通过这种方式发生。
提出这个问题的另一种方法是:如果使用相同的访问方法,Oracle是否会以相同的顺序返回结果? (并且运行之间没有数据变化。)
例如,如果查询使用全表扫描并按4/3/2/1的顺序返回行,那么总是会按顺序返回行吗?如果索引范围扫描以1/2/3/4的顺序返回行,那么它总是按顺序返回行吗?无论实际订单是什么,只要订单是确定性的。
(并行性可能会给这个问题增加一些复杂性。语句的整体顺序会因许多因素而有所不同。但对于锁定,我认为只有每个并行会话中的顺序很重要。再次,我的测试表明订单是确定性的,不会导致死锁。)
更新
我原来的问题有点泛泛。我最感兴趣的是,是否有可能在同一时间在两个不同的会话中运行类似update table_without_index set a = -1
的内容,并获得一个死锁? (我要问的是一次更新,而不是一系列更新。)
首先,让我演示完全相同的语句可能导致死锁。
创建表格,索引和一些数据:
为简单起见,我只更新同一列。在现实世界中会有不同的列,但我认为这不会改变任何内容。
请注意,我使用pctfree 0创建表,更新的值将占用更多空间,因此会有大量的行迁移。 (这是对@Tony Andrew的答案的回应,虽然我担心我的测试可能过于简单。而且,我认为我们不需要担心在更新之间插入行;只有一个更新会看到新行所以它不会导致死锁。除非新行也转移了其他一些东西。)
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
或其他任何会改变计划的内容,代码将永远不会导致死锁。 (我已经运行了一段时间的代码,没有错误。)
我问这个问题,因为我正在研究的系统偶尔会发生这种情况。它还没有失败,但我们想知道它是否真的安全,还是我们需要在更新中添加额外的锁定?
有人可以构建一个测试用例,其中同时运行并使用相同计划的单个更新语句会导致死锁吗?
答案 0 :(得分:4)
只有在查询中包含ORDER BY时,“订单”才是您的观点中的确定性 。从服务器的角度来看它是否是确定性 是一个实现细节,不能依赖它。
至于锁定,两个相同的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因PRIMARY KEY违规而失败。如果A回滚,则B成功。
可以为UPDATE和DELETE构建类似的示例。
重要的一点是,阻止不依赖于执行计划 - 无论Oracle如何选择优化您的查询,您都将始终拥有相同的阻止行为。您可能需要阅读Automatic Locks in DML Operations了解更多信息。
至于 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
或者,可能使用不同顺序修改多行的语句和一些非常不幸的时间(有人可以确认吗?)。
在回答您的问题更新时,让我做一个普遍的观察:如果并发执行线程以一致的顺序锁定对象,则死锁是不可能的。对于任何类型的锁定都是如此,无论是平均多线程程序中的互斥锁(例如,请参阅Herb Sutter's thoughts on Lock Hierarchies)还是数据库。一旦你以任何两个锁被“翻转”的方式改变顺序,就会引入死锁的可能性。
在不扫描索引的情况下,您将按顺序更新(并锁定)行,并将索引更新为另一个订单。所以,这可能就是你的情况:
alter session set optimizer_index_cost_adj = 1;
你会看到。)。[X]虽然我不会依赖具有保证订单的全表扫描 - 但这可能只是当前Oracle在这些特定情况下的工作方式,而未来某些Oracle或不同情况可能会产生不同的行为。 / em>的
因此,索引的存在是偶然的 - 真正的问题是排序。恰好在UPDATE中的排序可能会受到索引的影响,但如果我们能够以另一种方式影响排序,我们会得到类似的结果。
由于UPDATE没有ORDER BY,因此无法单独通过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;
/
答案 1 :(得分:1)
返回行的顺序不确定。一行可以在更新后“迁移”到不同的块,在这种情况下,它将出现在全表扫描结果中的不同位置。或者(可能更有可能)可以在两个现有行之间插入新行。