SELECT FOR UPDATE 与子查询导致死锁

Posted

技术标签:

【中文标题】SELECT FOR UPDATE 与子查询导致死锁【英文标题】:SELECT FOR UPDATE with Subquery results in Deadlock 【发布时间】:2014-04-17 07:35:08 【问题描述】:

我有查询从不同会话执行时导致死锁。

TAB1 (ID, TARGET, STATE, NEXT) AND ID is primary key

Column ID is the primary key.
SELECT * 
FROM 
TAB1 WHERE NEXT = (SELECT MIN(NEXT) FROM TAB1 WHERE TARGET=? AND STATE=?) 
AND TARGET=? AND STATE=? FOR UPDATE

在 Oracle 跟踪文件中,我看到了以下语句:

DEADLOCK DETECTED
Current SQL statement for this session:
SELECT ID, TARGET, NEXT, STATE FROM TAB1 
WHERE NEXT=(SELECT MIN(NEXT) FROM TAB1 WHERE (TARGET='$any') AND ( STATE = 0))
AND (TARGET='$any')
AND (STATE = 0) FOR UPDATE
The following deadlock is not an ORACLE error. It is a
deadlock due to user error in the design of an application
or from issuing incorrect ad-hoc SQL. The following
information may aid in determining the deadlock:
Deadlock graph:
                       ---------Blocker(s)--------  ---------Waiter(s)---------
Resource Name          process session holds waits  process session holds waits
TX-00010012-0102905b        54     474     X             52     256           X
TX-000a0005-00a30961        52     256     X             54     474           X

session 474: DID 0001-0036-00000002 session 256: DID 0001-0034-00000002
session 256: DID 0001-0034-00000002 session 474: DID 0001-0036-00000002
Rows waited on:
Session 256: obj - rowid = 00013181 - AAATGBAAzAABtPTAAI
  (dictionary objn - 78209, file - 51, block - 447443, slot - 8)
Session 474: obj - rowid = 00013181 - AAATGBAAzAABtPUAAJ
  (dictionary objn - 78209, file - 51, block - 447444, slot - 9)
Information on the OTHER waiting sessions:
Session 256:
  pid=52 serial=58842 audsid=43375302 user: 106/B2B_ISINTERNAL
  O/S info: user: admwmt, term: spl099wmt04.compucom.local, ospid: , machine: spl099wmt04.compucom.local/10.16.0.41
            program: JDBC Connect Client
Current SQL Statement:
SELECT ID, TARGET, NEXT, STATE FROM TAB1 
WHERE NEXT=(SELECT MIN(NEXT) FROM TAB1 WHERE (TARGET='$any') AND ( STATE = 0))
AND (TARGET='$any')
AND (STATE = 0) FOR UPDATE
End of information on OTHER waiting sessions.
===================================================

有什么办法可以避免这种情况吗?重写查询还是索引?

【问题讨论】:

删除了 sql-server 标签,因为该问题与 Oracle 有关 【参考方案1】:

我认为原因可能是您实际上使用 FOR UPDATE 子句选择了同一个表两次,一次在主查询中,一次在子查询中。

【讨论】:

是的,我需要该子查询来选择 min(NEXT)。查询是选择具有最小 NEXT(时间戳)和输入中指定的目标/状态的行。 我不是 Oracle 专家,但概念与其他 RDBM 相同。死锁是两个相互竞争的进程在继续之前等待对方完成。在您的情况下,似乎 sessionid 256 正在与 474 竞争。其中一个会话正在尝试选择一堆行并获取 UPDATE 锁。不确定消息是否显示另一个,但它可能是相同的语句。您需要从开始选择到更新的过程中更改您的事务流程。如果您从头到尾发布整个过程,将会很有帮助。 我有可以被多个线程调用的 java method()。 Java 方法执行以下事务。 1.获取连接(自动提交错误)。 2.执行上面的SELECT FOR UPDATE查询。 3.如果有一行,则做一些业务逻辑,更新NEXT(timestamp)列并Commit。【参考方案2】:

更新

与其尝试准确猜测 Oracle 如何检索行并强制执行计划,不如使用可用的UPDATE FOR locking features 之一可能更容易。

NOWAITSKIP LOCKED 应该能够解决问题。尽管使用NOWAIT,您可能需要添加一些应用程序逻辑以在出错后重试。


由于存在绑定变量,同一 SQL 语句可能有多个执行计划。这通常是一件好事,例如考虑这样的查询:select * from tab where status = ?。全表扫描最适合流行状态,索引扫描更适合稀有状态。但是,如果一个计划使用索引而另一个计划使用表,则同一条语句将以不同的顺序检索资源,从而可能导致死锁。

强制语句始终使用相同的计划将防止死锁。

首先,您需要确认我关于多个执行计划的理论是正确的。在此查询中查找多行,具体为同一 SQL_ID 查找不同的plan_hash_values。

select child_number, plan_hash_value, gv$sql.*
from gv$sql
where sql_text like '%NEXT=(SELECT%';

然后就是强制语句始终使用相同的计划。一种简单的方法是找到修复特定计划的大纲,并为两个语句使用相同的提示集。希望强制计划仍然适用于所有绑定变量集。

select *
from table(dbms_xplan.display_cursor(
    sql_id => '<SQL_ID from above>',
    cursor_child_no => <child_number from above>,
    format => '+outline')
);

【讨论】:

嗨,我有多行但具有相同的 plan_hash_value。查询大纲时。其显示:计划哈希值:3867517551 ------------------------------- |身份证 |操作 | ------------------------------------------- | 0 |选择声明 | | 1 |更新 | | 2 |缓冲区排序 | |* 3 |表访问完全 | | 4 |排序聚合 | |* 5 |表访问已满| ------------------------------------------- 可能是另一个计划在共享池中老化。您还可以在 AWR 中查找多个计划。首先确保您有足够的历史记录,检查select * from dba_hist_snapshot; 它是否延伸到死锁发生的日期。如果它回溯到足够远,则运行select * from table(dbms_xplan.display_awr(sql_id =&gt; '&lt;sql id above&gt;'));。这将显示是否有任何历史plan_hash_values。如果真的只有一个计划,那么这可能与我的老问题here有关。 这个查询有帮助吗? SELECT * FROM TAB1 WHERE TXID = (SELECT * FROM TAB1 WHERE STATE=? AND TARGET=? ORDER BY NEXT ASC) WHERE ROWNUM = 1) 进行更新

以上是关于SELECT FOR UPDATE 与子查询导致死锁的主要内容,如果未能解决你的问题,请参考以下文章

甲骨文。无法理解 FOR 如何与子查询 SELECT INTO 一起使用

同一张表上的两个“SELECT FOR UPDATE”语句会导致死锁吗?

select for update 如何适用于具有多行/结果的查询?

postgresql是多行原子的SELECT FOR UPDATE吗?

SELECT FOR UPDATE 用于锁定查询

select for update