1. Oracle表锁问题概述
在日常的Oracle数据库运维工作中,表锁问题是最常见的故障之一。当多个会话同时访问同一数据对象时,Oracle会通过锁定机制来维护数据一致性。但有时由于程序异常、事务未提交或人为操作失误,会导致锁无法正常释放,进而影响业务系统的正常运行。
作为DBA,我们需要掌握快速定位和解决表锁问题的技能。本文将详细介绍从锁信息查询到会话终止的完整处理流程,并分享我在实际工作中总结的实用技巧。
2. 锁类型与查询方法
2.1 Oracle常见锁类型
Oracle数据库中的锁主要分为以下几类:
- 行级锁(TX锁):事务锁,当修改数据行时自动获取
- 表级锁(TM锁):在对表进行DDL操作或某些DML操作时获取
- 排他锁(X锁):阻止其他会话获取任何类型的锁
- 共享锁(S锁):允许其他会话获取共享锁,但阻止排他锁
锁的兼容性矩阵如下:
| 请求\持有 | 无锁 | S锁 | X锁 |
|---|---|---|---|
| S锁 | 允许 | 允许 | 拒绝 |
| X锁 | 允许 | 拒绝 | 拒绝 |
2.2 查询被锁对象信息
要解决锁问题,首先需要准确定位锁定的对象和持有锁的会话。以下是两个最常用的查询语句:
sql复制-- 查询被锁定的对象信息
SELECT b.owner, b.object_name, a.session_id, a.locked_mode
FROM v$locked_object a, dba_objects b
WHERE b.object_id = a.object_id;
这个查询会返回:
- 对象所有者(owner)
- 对象名称(object_name)
- 持有锁的会话ID(session_id)
- 锁模式(locked_mode)
提示:locked_mode字段的数值对应不同的锁类型,常见值包括:
- 2:行共享锁(RS)
- 3:行排他锁(RX)
- 4:共享锁(S)
- 5:共享行排他锁(SRX)
- 6:排他锁(X)
3. 定位持有锁的会话
3.1 查询会话详细信息
获取被锁对象信息后,需要进一步查询持有这些锁的会话详情:
sql复制-- 查询持有锁的会话信息
SELECT b.username, b.sid, b.serial#, b.logon_time, b.status, b.machine, b.program
FROM v$locked_object a, v$session b
WHERE a.session_id = b.sid
ORDER BY b.logon_time;
这个查询返回的信息包括:
- 数据库用户名(username)
- 会话ID(sid)和序列号(serial#)
- 登录时间(logon_time)
- 会话状态(status)
- 客户端机器名(machine)
- 客户端程序名(program)
3.2 分析会话活动
在实际工作中,我通常会结合以下查询获取更多会话活动信息:
sql复制-- 查询会话正在执行的SQL语句
SELECT s.sid, s.serial#, s.username, s.status,
q.sql_text, s.machine, s.program
FROM v$session s, v$sql q
WHERE s.sql_id = q.sql_id
AND s.sid IN (SELECT session_id FROM v$locked_object);
这个查询可以帮助我们了解:
- 会话当前执行的SQL语句
- 会话状态(active/inactive)
- 客户端来源信息
4. 解锁被锁定的表
4.1 终止会话的标准方法
确认需要终止的会话后,可以使用以下命令释放锁:
sql复制-- 终止指定会话
ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;
例如:
sql复制ALTER SYSTEM KILL SESSION '759,14518' IMMEDIATE;
注意事项:
- IMMEDIATE参数会立即终止会话,而不等待当前事务完成
- 终止会话前,应评估其对业务的影响
- 对于重要事务,建议先联系相关用户确认
4.2 替代终止会话的方法
在某些情况下,终止会话可能不是最佳选择。可以考虑以下替代方案:
-
联系用户提交或回滚事务:
sql复制-- 查询会话的事务状态 SELECT s.sid, s.serial#, s.username, t.status, t.start_time FROM v$session s, v$transaction t WHERE s.taddr = t.addr AND s.sid = [会话ID]; -
设置会话超时:
sql复制-- 设置空闲会话超时(单位:分钟) ALTER PROFILE DEFAULT LIMIT IDLE_TIME 30; -
使用资源管理器限制会话:
sql复制-- 创建资源计划限制长时间运行的会话 BEGIN DBMS_RESOURCE_MANAGER.CREATE_PENDING_AREA(); DBMS_RESOURCE_MANAGER.CREATE_CONSUMER_GROUP( CONSUMER_GROUP => 'LONG_RUNNING', COMMENT => '长时间运行会话组'); DBMS_RESOURCE_MANAGER.SUBMIT_PENDING_AREA(); END; /
5. 高级锁问题处理技巧
5.1 处理分布式锁
在分布式数据库环境中,锁问题可能更加复杂。可以使用以下查询检查分布式事务:
sql复制-- 查询分布式事务
SELECT local_tran_id, global_tran_id, state, status
FROM dba_2pc_pending;
对于挂起的分布式事务,可以尝试:
sql复制-- 提交挂起的分布式事务
EXECUTE DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('[事务ID]');
-- 回滚挂起的分布式事务
EXECUTE DBMS_TRANSACTION.ROLLBACK_FORCE('[事务ID]');
5.2 处理死锁问题
Oracle会自动检测并解决死锁,但我们可以通过以下方式监控:
sql复制-- 查询死锁历史
SELECT * FROM dba_blockers;
SELECT * FROM dba_waiters;
-- 查询最近的死锁信息
SELECT * FROM v$diag_alert_ext
WHERE message_text LIKE '%deadlock%'
ORDER BY originating_timestamp DESC;
5.3 使用Oracle Enterprise Manager
对于图形界面用户,Oracle Enterprise Manager提供了直观的锁管理界面:
- 登录OEM控制台
- 导航到"Performance" → "Blocking Sessions"
- 查看锁等待链
- 可以选择终止会话或查看锁详情
6. 预防锁问题的策略
6.1 应用设计最佳实践
-
事务设计原则:
- 保持事务短小精悍
- 避免在事务中执行用户交互
- 按照固定顺序访问表,避免死锁
-
锁提示使用:
sql复制-- 使用NOWAIT选项避免等待锁 SELECT * FROM employees FOR UPDATE NOWAIT; -- 使用SKIP LOCKED跳过锁定的行 SELECT * FROM employees FOR UPDATE SKIP LOCKED;
6.2 监控与预警设置
建议设置以下监控:
sql复制-- 创建锁监控作业
BEGIN
DBMS_SCHEDULER.CREATE_JOB (
job_name => 'MONITOR_LOCKS',
job_type => 'PLSQL_BLOCK',
job_action => 'BEGIN
IF (SELECT COUNT(*) FROM v$locked_object) > 10 THEN
-- 发送警报邮件
NULL;
END IF;
END;',
start_date => SYSTIMESTAMP,
repeat_interval => 'FREQ=MINUTELY;INTERVAL=5',
enabled => TRUE);
END;
/
6.3 性能优化建议
-
索引优化:
- 确保查询使用适当的索引,减少锁范围
- 考虑使用位图索引减少锁争用
-
隔离级别调整:
sql复制-- 设置事务隔离级别 ALTER SESSION SET ISOLATION_LEVEL = SERIALIZABLE; -
使用物化视图:
- 对报表查询使用物化视图减少锁争用
7. 常见问题排查
7.1 无法终止会话的情况
有时会遇到会话无法终止的问题,可以尝试以下方法:
-
在操作系统级别终止进程:
sql复制-- 查询会话对应的操作系统进程 SELECT s.sid, s.serial#, p.spid, s.program FROM v$session s, v$process p WHERE s.paddr = p.addr AND s.sid = [会话ID];然后在操作系统级别使用kill命令终止进程
-
使用oradebug工具:
sql复制-- 连接到sysdba账户 ORADEBUG SETMYPID ORADEBUG UNLIMIT ORADEBUG KILL [系统PID]
7.2 锁等待超时处理
如果遇到锁等待超时错误(ORA-00054),可以:
-
调整DDL锁等待时间:
sql复制-- 设置DDL锁等待时间(单位:秒) ALTER SYSTEM SET ddl_lock_timeout=30; -
使用NOWAIT选项:
sql复制LOCK TABLE employees IN EXCLUSIVE MODE NOWAIT;
7.3 系统锁问题诊断
对于系统级别的锁问题,可以使用以下诊断视图:
sql复制-- 查询锁等待链
SELECT
(SELECT username FROM v$session WHERE sid = h.session_id) blocker,
h.session_id blocker_sid,
w.session_id waiter_sid,
(SELECT username FROM v$session WHERE sid = w.session_id) waiter,
w.lock_type,
w.mode_held,
w.mode_requested
FROM dba_blockers b, dba_waiters w, v$lock h
WHERE b.holding_session = h.session_id
AND w.holding_session = h.session_id;
8. 实战案例分享
8.1 案例一:长时间运行的事务
现象:用户报告某个关键表被锁定,无法执行更新操作。
排查步骤:
-
查询被锁对象:
sql复制SELECT object_name, session_id, oracle_username, os_user_name FROM v$locked_object l, dba_objects o WHERE l.object_id = o.object_id AND o.object_name = 'EMPLOYEES'; -
发现是某个ETL进程持有锁,查询其活动:
sql复制SELECT s.sid, s.serial#, s.program, s.module, s.action, s.client_info, s.logon_time, sql.sql_text FROM v$session s, v$sql sql WHERE s.sql_id = sql.sql_id AND s.sid = [锁持有会话ID]; -
确认该ETL进程已运行超过4小时,决定终止:
sql复制ALTER SYSTEM KILL SESSION '123,45678' IMMEDIATE;
经验总结:对于ETL作业,应该设置合理的超时时间,并在非高峰期运行。
8.2 案例二:应用程序连接泄漏
现象:系统出现大量inactive会话持有锁。
排查步骤:
-
查询非活动会话持有的锁:
sql复制SELECT s.sid, s.serial#, s.status, s.last_call_et, o.object_name, l.locked_mode FROM v$session s, v$lock l, dba_objects o WHERE s.sid = l.sid AND l.id1 = o.object_id AND s.status = 'INACTIVE' AND s.last_call_et > 3600; -- 超过1小时不活动 -
发现是某应用服务器连接池配置不当,连接未正确关闭。
解决方案:
-
调整应用连接池配置:
properties复制# 设置最大空闲时间 maxIdleTime=1800000 # 30分钟 # 启用泄漏检测 leakDetectionThreshold=60000 # 1分钟 -
清理现有无效会话:
sql复制-- 生成终止会话脚本 SELECT 'ALTER SYSTEM KILL SESSION '''||sid||','||serial#||''' IMMEDIATE;' FROM v$session WHERE status = 'INACTIVE' AND last_call_et > 3600 AND program LIKE '%APP_SERVER%';
经验总结:定期监控应用连接池使用情况,设置合理的超时参数。
9. 自动化监控脚本
以下是我在日常工作中使用的锁监控脚本,可以定期运行或集成到监控系统中:
sql复制-- 锁监控报表脚本
SET LINESIZE 200
SET PAGESIZE 100
COL owner FOR A15
COL object_name FOR A30
COL username FOR A15
COL machine FOR A25
COL program FOR A20
COL sql_text FOR A50
SELECT
lo.session_id,
s.username,
s.machine,
s.program,
s.logon_time,
o.owner,
o.object_name,
o.object_type,
lo.locked_mode,
TO_CHAR(s.last_call_et/60, '999.99') "IDLE_MIN",
q.sql_text
FROM
v$locked_object lo,
dba_objects o,
v$session s,
v$sql q
WHERE
lo.object_id = o.object_id
AND lo.session_id = s.sid
AND s.sql_id = q.sql_id(+)
ORDER BY
s.last_call_et DESC;
可以将此脚本保存为monitor_locks.sql,然后定期执行:
bash复制sqlplus / as sysdba @monitor_locks.sql
10. 性能考虑与最佳实践
在处理锁问题时,还需要考虑以下性能因素:
-
锁转换开销:Oracle在执行某些操作时需要转换锁类型,这会产生额外开销。例如,从行锁升级为表锁。
-
ITL槽争用:初始化事务槽(ITL)不足会导致等待。可以通过以下方式调整:
sql复制-- 增加表的ITL槽数量 ALTER TABLE employees INITRANS 10; -
避免热点块:频繁更新的索引根块或表头块可能成为热点。考虑:
- 使用反向键索引
- 增加自由列表组
- 使用哈希分区分散I/O
-
应用层优化:
- 实现乐观锁替代悲观锁
- 使用队列处理并发更新
- 考虑读写分离架构
在实际工作中,我发现80%的锁问题都源于应用设计不当。因此,与开发团队密切合作,建立数据库访问规范,是预防锁问题的关键。