首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >相同的访问方法会出现死锁吗?

相同的访问方法会出现死锁吗?
EN

Stack Overflow用户
提问于 2012-02-09 17:55:29
回答 2查看 2.9K关注 0票数 4

如果两个并发DML语句修改相同的数据并使用相同的访问方法,是否可能出现死锁?

根据我的测试,以及我对Oracle如何工作的猜测,答案是否定的。

但我想百分之百肯定。我正在寻找一个官方的消息来源,它说死锁不可能以这种方式发生,或者一个测试用例来证明死锁可以以这种方式发生。

问这个问题的另一种方法是:如果使用相同的访问方法,Oracle是否总是以相同的顺序返回结果?(并且在运行之间没有数据更改。)

例如,如果查询使用完整的表扫描并以4/3/2/1的顺序返回行,那么它是否总是按照该顺序返回行?如果索引范围扫描按1/2/3/4的顺序返回行,它是否总是按照该顺序返回行?不管实际的顺序是什么,只是顺序是确定性的。

(并行可能会给这个问题增加一些复杂性。根据许多因素,声明的总体顺序将有所不同。但对于锁定,我认为只有在每个平行会议内的顺序是重要的。同样,我的测试表明,顺序是确定性的,不会导致死锁。)

更新

我原来的问题有点笼统。我最感兴趣的是,是否有可能同时在两个不同的会话中运行类似update table_without_index set a = -1的程序,并获得一个死锁?(我问的是一次更新,而不是一系列更新。)

首先,让我证明,完全相同的说法会造成僵局。

创建一个表、索引和一些数据:

为了简单起见,我只更新同一列。在现实世界中,可能会有不同的列,但我认为这不会改变任何事情。

请注意,我使用pctfree 0创建了表,更新后的值将占用更多的空间,因此将有大量的行迁移。(这是对@Tony的回答的回应,尽管我担心我的测试可能过于简单。另外,我认为我们不需要担心在更新之间插入行;只有一个更新会看到新行,这样就不会导致死锁。除非新行也转移了一堆其他东西。)

代码语言:javascript
运行
复制
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中运行此块:

代码语言:javascript
运行
复制
begin
    while true loop
        update deadlock_test set a = -99999999999999999999 where a > 0;
        rollback;
    end loop;
end;
/

在会话2中运行此块:

代码语言:javascript
运行
复制
--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语句会导致死锁?

EN

回答 2

Stack Overflow用户

发布于 2012-02-09 18:19:24

从您的角度来看,只有在查询中包含ORDER,"order“才是决定性的。从服务器的角度来看,它是否是确定性的,这是一个实现细节,不需要依赖。

至于锁定,两个相同的DML语句可以彼此阻塞(但不是死锁)。例如:

代码语言:javascript
运行
复制
CREATE TABLE THE_TABLE (
    ID INT PRIMARY KEY
);

交易A:

代码语言:javascript
运行
复制
INSERT INTO THE_TABLE VALUES(1);

交易B:

代码语言:javascript
运行
复制
INSERT INTO THE_TABLE VALUES(1);

此时,事务B是停止的,直到事务A提交或回滚。如果A提交,则B由于主键违反而失败。如果A回滚,B就成功了。

可以为更新和删除构造类似的示例。

重要的一点是,阻塞将不取决于执行计划--无论Oracle如何选择优化查询,始终具有相同的阻塞行为。您可能希望阅读有关DML操作中的自动锁的更多信息。

至于dead-locks,可以使用多个语句来实现。例如:

代码语言:javascript
运行
复制
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中,死锁是不可能的。这对于任何类型的锁定都是正确的,无论是普通多线程程序中的互斥(例如,参见赫伯·萨特的锁等级思想)还是数据库。一旦您以“翻转”任何两个锁的方式更改顺序,就会引入死锁的可能性。

在不扫描索引的情况下,您将按一个顺序更新(和锁定)行,并在另一个顺序中使用索引更新行。因此,在您的情况下,可能会发生这样的情况:

  • 如果禁用对两个并发事务的索引扫描,它们都会以相同的X顺序锁定行,因此不可能出现死锁。
  • 如果只为一个事务启用索引扫描,则它们不再以相同的顺序锁定行,因此有可能出现死锁。
  • 如果对这两个事务启用索引扫描,那么它们都是以相同的顺序锁定行,并且不可能出现死锁(请继续在两个会话中尝试alter session set optimizer_index_cost_adj = 1;,您将看到)。

虽然我不会依赖具有保证顺序的全表扫描--这可能正是当前Oracle在这些特定情况下的工作方式,而且将来的Oracle或不同的环境可能会产生不同的行为。

因此,指数的存在是偶然的-真正的问题是排序。当然,更新中的排序可能会受到索引的影响,但是如果我们能够以另一种方式影响排序,我们就会得到类似的结果。

由于UPDATE没有ORDER,所以不能真正保证只按UPDATE锁定的顺序。但是,如果将锁定与更新分开,则可以保证锁定顺序:

代码语言:javascript
运行
复制
SELECT ... ORDER BY ... FOR UPDATE;

虽然原始代码在我的Oracle 10环境中导致死锁,但以下代码没有:

会话1:

代码语言:javascript
运行
复制
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:

代码语言:javascript
运行
复制
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;
/
票数 4
EN

Stack Overflow用户

发布于 2012-02-09 18:12:03

返回行的顺序不是确定性的。行在更新后有可能“迁移”到不同的块,在这种情况下,行将在全表扫描的结果中出现在不同的位置。或者(可能更有可能)在两个现有行之间插入一个新行。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/9216150

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档