问题场景
在Oracle数据库日常运维中,我们经常会遇到数据记录被锁定的情况。当一个用户正在修改某条记录时,Oracle会自动对该记录加锁,以防止其他用户同时修改造成数据不一致。但有时由于程序异常、事务未正常提交或用户误操作,会导致锁长时间持有,影响其他用户的正常操作。
查询被锁记录
要解决锁问题,首先需要查看当前数据库中的锁情况。以下是常用的查询语句:
sql
-- 查询当前被锁的对象和会话信息
SELECT
t2.username,
t2.sid,
t2.serial#,
t2.logon_time,
t1.object_id,
t1.oracle_username,
t1.os_user_name,
t1.process,
t1.locked_mode
FROM
v$locked_object t1,
v$session t2
WHERE
t1.session_id = t2.sid
ORDER BY
t2.logon_time;
查询结果字段说明
字段名 说明
username 数据库用户名
sid 会话ID
serial# 会话序列号
logon_time 登录时间
object_id 被锁对象ID
oracle_username Oracle用户名
os_user_name 操作系统用户名
process 操作系统进程ID
locked_mode 锁模式
更详细的锁信息查询
sql
-- 查询更详细的锁信息,包括SQL语句
SELECT
s.username,
s.sid,
s.serial#,
s.status,
l.object_id,
o.object_name,
o.object_type,
l.locked_mode,
s.machine,
s.program,
s.sql_id,
sq.sql_text
FROM
v$locked_object l,
dba_objects o,
v$session s,
v$sql sq
WHERE
l.object_id = o.object_id
AND l.session_id = s.sid
AND s.sql_id = sq.sql_id(+)
ORDER BY
s.logon_time;
sql
强制删除锁记录
确认了被锁的会话后,可以使用以下命令强制终止会话:
sql
-- 终止指定会话
ALTER SYSTEM KILL SESSION 'sid,serial#';
-- 示例:终止sid=123, serial#=456的会话
ALTER SYSTEM KILL SESSION '123,456';
立即终止会话
如果会话无法正常终止,可以加上IMMEDIATE选项:
sql
ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;
完整处理流程
步骤1:查询锁信息
首先执行查询语句,找出锁定的对象和相关会话。
步骤2:分析锁原因
查看:
哪个用户持有锁
锁了多长时间
执行的是什么SQL语句
是否可以通过联系用户解决
步骤3:尝试正常解决
如果可能,联系持有锁的用户:
请其提交或回滚事务
检查应用程序是否正常
步骤4:强制终止会话
如果无法联系用户或问题急需解决,使用KILL SESSION命令。
步骤5:验证结果
终止后再次查询锁信息,确认锁已被释放。
注意事项
- 风险提示
数据一致性:强制终止会话可能导致未提交的事务回滚
业务影响:正在执行的操作会被中断
依赖会话:某些会话可能是关键业务进程
- 最佳实践
sql
sql
-- 在终止前,可以先查看会话的详细信息
SELECT
sid,
serial#,
username,
program,
machine,
status,
to_char(logon_time, 'yyyy-mm-dd hh24:mi:ss') as logon_time,
last_call_et/3600 as hours_active
FROM
v$session
WHERE
sid = [目标SID];
-- 终止前记录信息,便于审计
INSERT INTO session_kill_audit
(kill_time, sid, serial#, username, killer, reason)
VALUES
(SYSDATE, 123, 456, 'USER_A', USER, '会话长时间持有锁');
- 防止锁问题的建议
sql
sql
-- 1. 设置会话超时
ALTER PROFILE DEFAULT LIMIT IDLE_TIME 30; -- 30分钟空闲超时
-- 2. 监控长时间运行的会话
SELECT
s.sid,
s.serial#,
s.username,
s.program,
s.status,
s.last_call_et/60 as minutes_idle,
t.start_time,
t.used_ublk
FROM
v$session s,
v$transaction t
WHERE
s.taddr = t.addr(+)
AND s.status = 'ACTIVE'
AND s.last_call_et > 300 -- 超过5分钟
ORDER BY
s.last_call_et DESC;
-- 3. 使用NOWAIT选项避免等待锁
SELECT * FROM table_name FOR UPDATE NOWAIT;
高级锁管理
查询锁等待链
sql
sql
-- 查看锁等待关系
SELECT
(SELECT username FROM v$session WHERE sid = a.sid) blocker,
a.sid,
' is blocking ',
(SELECT username FROM v$session WHERE sid = b.sid) blocked,
b.sid
FROM
v$lock a,
v$lock b
WHERE
a.block = 1
AND b.request > 0
AND a.id1 = b.id1
AND a.id2 = b.id2;
查询具体的锁类型
sql
sql
-- 锁模式说明
SELECT
lm.locked_mode,
lm.mode_description,
COUNT(*) as lock_count
FROM
v$locked_object lo,
(SELECT 0 locked_mode, 'None' mode_description FROM dual UNION ALL
SELECT 1, 'Null (NULL)' FROM dual UNION ALL
SELECT 2, 'Row-S (SS)' FROM dual UNION ALL
SELECT 3, 'Row-X (SX)' FROM dual UNION ALL
SELECT 4, 'Share (S)' FROM dual UNION ALL
SELECT 5, 'S/Row-X (SSX)' FROM dual UNION ALL
SELECT 6, 'Exclusive (X)' FROM dual) lm
WHERE
lo.locked_mode = lm.locked_mode
GROUP BY
lm.locked_mode, lm.mode_description
ORDER BY
lm.locked_mode;
自动化监控脚本
可以创建定期作业监控锁情况:
sql
sql
-- 创建锁监控表
CREATE TABLE lock_monitor (
monitor_time DATE,
username VARCHAR2(30),
sid NUMBER,
serial# NUMBER,
object_name VARCHAR2(128),
locked_mode NUMBER,
session_status VARCHAR2(8),
minutes_active NUMBER
);
sql
-- 创建监控作业
BEGIN
DBMS_SCHEDULER.create_job (
job_name => 'LOCK_MONITOR_JOB',
job_type => 'PLSQL_BLOCK',
job_action => 'BEGIN
INSERT INTO lock_monitor
SELECT
SYSDATE,
s.username,
s.sid,
s.serial#,
o.object_name,
lo.locked_mode,
s.status,
s.last_call_et/60
FROM
v$locked_object lo,
dba_objects o,
v$session s
WHERE
lo.object_id = o.object_id
AND lo.session_id = s.sid
AND s.last_call_et > 300; -- 超过5分钟
COMMIT;
END;',
start_date => SYSTIMESTAMP,
repeat_interval => 'FREQ=MINUTELY;INTERVAL=5',
enabled => TRUE
);
END;
总结:
Oracle锁管理是DBA日常工作的重要组成部分。正确处理锁问题需要:
准确诊断:使用正确的查询找出问题根源
谨慎操作:在强制终止前评估影响
预防为主:通过监控和优化减少锁问题发生
文档记录:所有操作应有记录,便于追溯和分析