在 Oracle 数据库中,死锁(Deadlock) 是指两个或多个事务因互相持有对方需要的资源(如行锁、表锁)而无法继续执行的状态。此时,每个事务都在等待其他事务释放资源,形成“循环等待”,若没有外部干预,这些事务将永远无法完成。
锁类型:
1、共享锁(Share Locks,即S锁):加了共享锁的数据对象可以被其他事务读取,但不能修改。数据库利用这两种基本的锁类型来对数据库的事务进行并发控制。
2、排它锁(Exclusive Locks,即X锁):当数据对象被加上排它锁时,其他的事务不能对它读取和修改。
3、独占锁:在共享锁的基础上,升级为独占锁。
4、更新锁:所有用户都可以读,但我将来可能会做更新操作,我已经获取了从共享锁(用来读)到排他锁(用来更新)的资格。一个事物只能有一个更新锁获此资格。
一、死锁的典型场景示例
以下是一个常见的死锁场景:
事务 A 执行:
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1; -- 行锁 R1 UPDATE accounts SET balance = balance + 100 WHERE user_id = 2; -- 请求行锁 R2事务 B 同时执行:
UPDATE accounts SET balance = balance - 200 WHERE user_id = 2; -- 行锁 R2 UPDATE accounts SET balance = balance + 200 WHERE user_id = 1; -- 请求行锁 R1
此时:
事务 A 持有 R1,请求 R2;
事务 B 持有 R2,请求 R1;
两者形成循环等待,Oracle 会检测到死锁并自动回滚其中一个事务(通常选择“代价较小”的事务,即持有较少锁或 undo 数据较少的事务)。
Oracle 提供了多种工具和视图来检测死锁:
1. AWR 报告与 ASH(活动会话历史)
死锁事件会被记录在 AWR(自动工作负载仓库)报告中,通过分析 AWR_REPORT_HTML可定位死锁发生的时间、涉及的事务和 SQL 语句。
ASH 存储了最近 7 天的会话活动数据,可通过 DBA_HIST_ACTIVE_SESS_HISTORY查看死锁相关的等待事件(如 enq: TX - row lock contention)。
在 Oracle 数据库中,死锁(Deadlock) 是指两个或多个事务因互相持有对方需要的资源(如行锁、表锁)而无法继续执行的状态。此时,每个事务都在等待其他事务释放资源,形成“循环等待”,若没有外部干预,这些事务将永远无法完成。
死锁的核心是资源的循环等待,通常由以下因素共同作用导致:
1. 事务的并发操作
多个事务同时修改或查询同一组资源(如表、行),且操作顺序不一致。例如:
事务 A 持有资源 X,并请求资源 Y;
事务 B 持有资源 Y,并请求资源 X;
此时,A 和 B 互相等待对方释放资源,形成死锁。
2. 锁的类型与模式冲突
Oracle 中锁的类型(如行锁、表锁)和模式(如共享锁 S、排他锁 X)若不兼容,会导致资源无法被其他事务获取。例如:
事务 A 对行 R 加排他锁(X 锁,用于 UPDATE/DELETE);
事务 B 尝试对行 R 加共享锁(S 锁,用于 SELECT ... FOR UPDATE)或排他锁;
此时,事务 B 会被阻塞,直到事务 A 提交或回滚。若此时事务 B 已持有其他资源,而该资源被事务 A 需要,则可能引发死锁。
3. 长事务或未及时提交/回滚
事务执行时间过长(如批量操作、复杂查询),导致锁持有时间增加,其他事务等待时间变长,增加了循环等待的概率。
4. 索引缺失或全表扫描
若 SQL 语句未使用索引(如 UPDATE/DELETE 操作基于非索引列),Oracle 可能升级为表级锁(如 TM 锁),导致更大范围的资源竞争。例如:
事务 A 对表 T 的某行执行 UPDATE(未使用索引),Oracle 可能升级为对表 T 加共享锁(S 锁);
事务 B 尝试对表 T 加排他锁(X 锁,如执行 DROP TABLE),会被阻塞;
若此时事务 A 还需要其他资源(如另一个表的锁),而该资源被事务 B 持有,则可能死锁。
5. 不合理的资源访问顺序
多个事务以不同顺序访问相同资源。例如:
事务 A 先更新表 T1 的行 R1,再更新表 T2 的行 R2;
事务 B 先更新表 T2 的行 R2,再更新表 T1 的行 R1;
若两者同时执行,可能因互相持有对方需要的行锁而死锁。
二、死锁的典型场景示例
以下是一个常见的死锁场景:
事务 A 执行:
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1; -- 行锁 R1 UPDATE accounts SET balance = balance + 100 WHERE user_id = 2; -- 请求行锁 R2事务 B 同时执行:
UPDATE accounts SET balance = balance - 200 WHERE user_id = 2; -- 行锁 R2 UPDATE accounts SET balance = balance + 200 WHERE user_id = 1; -- 请求行锁 R1
此时:
事务 A 持有 R1,请求 R2;
事务 B 持有 R2,请求 R1;
两者形成循环等待,Oracle 会检测到死锁并自动回滚其中一个事务(通常选择“代价较小”的事务,即持有较少锁或 undo 数据较少的事务)。
查询死锁位置并释放死锁:
select sess.sid,sess.serial#,lo.oracle_username, lo.os_user_name,ao.object_name, lo.locked_mode
from v$locked_object lo,dba_objects ao,v$session sess
where ao.object_id = lo.object_id and lo.session_id = sess.sid;
alter system kill session '738,1429';三、死锁的检测方法
Oracle 提供了多种工具和视图来检测死锁:
1、检查数据库确定是否存在死锁
select username, lockwait, status, machine, program
from v$session
where sid in (select session_id from v$locked_object);
Username:死锁语句所用的数据库用户;
Lockwait:死锁的状态,如果有内容表示被死锁。
Status:状态,active表示被死锁
Machine:死锁语句所在的机器。
Program:产生死锁的语句主要来自哪个应用程序。2、确定死锁后,可以检查是sql产生死锁等待
select sql_text
from v$sql
where hash_value in
(select sql_hash_value from v$session where sid in (select session_id from v$locked_object));3、查询未提交的事务sql
select s.sid,
s.username,
s.osuser,
s.program,
to_char(s.LOGON_TIME, 'yyyymmdd hh24:mi:ss') as LOGON_TIME,
to_char(t.START_DATE, 'yyyymmdd hh24:mi:ss') as START_DATE,
s.status,
(select q.SQL_TEXT from v$sql q where q.LAST_ACTIVE_TIME = t.START_DATE
and rownum <= 1) as SQL_TEXT
from v$session s,
v$transaction t
where s.sADDR = t.SES_ADDR;4、找不到对应用户,可以通过kill掉死锁的session进程
SELECT l.SESSION_ID, l.OS_USER_NAME, s.USERNAME, l.OBJECT_ID, l.ORACLE_USERNAME
FROM v$locked_object l,
v$session s
WHERE l.SESSION_ID = s.SID;5、根据SessionID查询锁表语句
select sql_text
from v$sql
where hash_value in
(select sql_hash_value from v$session where sid in (208));6、查看死锁
select sess.sid, sess.serial#, lo.oracle_username, lo.os_user_name, ao.object_name, lo.locked_mode, SESS.machine
from v$locked_object lo,
dba_objects ao,
v$session sess
where ao.object_id = lo.object_id
and lo.session_id = sess.sid;7、对v$locked_object被锁对象进行查询
SELECT l.session_id sid,
s.serial#,
l.locked_mode,
l.oracle_username,
l.os_user_name,
s.machine,
s.terminal,
o.object_name,
s.logon_time
FROM v$locked_object l, all_objects o, v$session s
WHERE l.object_id = o.object_id AND l.session_id = s.sid
ORDER BY sid, s.serial#;8、杀死该进程
alter system kill session '139,6151';
---139是查询结果中的SID字段,6151是查询结果中的SERIA