
好的,我们来详细解析 ORA-00099 错误。
📌 ORA-00099 错误详解
错误信息结构组成
标准的 ORA-00099 错误信息格式如下:
ORA-00099: waiting for resource resource_name timed out
其结构可以分解为:
ORA-00099: Oracle 数据库错误的唯一标识码。waiting for resource: 错误的核心描述,表明会话正在等待一个资源。resource_name: 一个占位符,指明了会话等待超时的具体资源的名称或类型(例如:'enq: TX - row lock contention')。timed out: 明确指出等待这个资源的时间已经超过了系统允许的阈值。
在某些版本或上下文中,您可能还会看到更具体的变体,例如:
ORA-00099: failed to get resource within timeout
错误含义与官方解释
官方解释:
- 原因 (Cause): 一个数据库会话(Session)请求一个资源(如一个锁),但该资源在系统内部定义的最大时间限制内一直无法被获取。在等待了指定的时间后,操作被中止以避免无限期等待。
- 行动 (Action): 这是一个内部超时错误。通常表示系统中存在严重的资源争用。建议调查导致资源不可用的根本原因,例如找出持有该资源并阻止当前会话的阻塞会话(Blocking Session)。
核心要点:这是一个资源等待超时错误。它不像死锁(ORA-00060)那样由数据库自动检测和解决,而是Oracle的一种“防僵死”机制。当会话等待某个资源的时间过长时,数据库会主动中断这次等待,抛出ORA-00099,以防止会话(以及可能依赖它的其他会话)无限期地挂起,从而影响系统整体可用性。
产生错误的场景与原因
典型场景:
此错误发生在高并发、存在资源竞争的数据库环境中,特别是当某个会话长时间持有关键资源而不释放时。
具体原因与示例:
-
行锁争用 (Row Lock Contention):
这是最常见的原因。一个事务(Transaction A)更新了一行数据但未提交(持有该行的排他锁),另一个事务(Transaction B)尝试更新同一行数据,就必须等待 Transaction A 释放锁。如果 Transaction A 长时间不提交或回滚,Transaction B 在等待一段时间后就会因超时而抛出 ORA-00099。错误示例:
-- Session 1: SQL> UPDATE employees SET salary = 15000 WHERE employee_id = 100; -- (但迟迟不执行 COMMIT 或 ROLLBACK) -- Session 2 (在等待一段时间后): SQL> UPDATE employees SET salary = 16000 WHERE employee_id = 100; -- ERROR at line 1: -- ORA-00099: waiting for resource 'enq: TX - row lock contention' timed out -
其他资源争用:
虽然较少见,但等待其他类型的资源(如某些特定的闩锁-Latch、缓冲区缓冲区忙等待等)也可能超时。 -
超时阈值:
这个超时时间是由 Oracle 内部控制的,通常与_ENQUEUE_TIMEOUT等隐藏参数相关,一般不建议用户修改。它不同于应用层设置的SQLNET.OUTBOUND_CONNECT_TIMEOUT等网络超时。
相关原理
- 锁机制 (Locking Mechanism):Oracle 使用锁来保证数据的一致性(Consistency)和并发性(Concurrency)。当一个事务修改数据时,它会获取一个排他锁(Exclusive Lock),防止其他事务修改相同的数据,直到它提交或回滚。
- 排队机制 (Enqueue Mechanism):
enq: TX - row lock contention中的 “enq” 指的是 “enqueue”,这是一种复杂的排队机制,用于管理对数据库资源(如表行、事务本身)的并发访问请求。当资源不可用时,请求会话会进入一个等待队列。 - 超时机制 (Timeout Mechanism):为了避免系统因等待而陷入完全停滞,Oracle 为大多数资源的等待设置了一个内部超时时间。如果等待时间超过此限制,正在等待的会话就会停止等待并抛出 ORA-00099 错误,同时它请求的操作也会失败。这是一种保护机制。
相关联的其他 ORA 错误
在处理锁和资源争用时,您可能会遇到相关错误:
- ORA-00060: deadlock detected while waiting for resource: 比 ORA-00099 更常见。表示两个或多个会话相互等待对方持有的资源,形成了一个循环等待的“死结”,数据库会自动选择一个会话作为“牺牲者”来回滚其事务以打破死锁。
- ORA-04021: timeout occurred while waiting to lock object: 在尝试编译或操作 PL/SQL 对象时,需要锁定该对象,但另一个会话正持有该对象的锁,导致等待超时。
问题定位与诊断分析
当遇到 ORA-00099 时,诊断目标是找到阻塞者 (Blocker)。
诊断步骤:
-
确认错误和资源:从错误信息中准确记录下超时的
resource_name(如'enq: TX - row lock contention')。 -
查询当前锁信息:使用数据字典视图来查找阻塞会话。
-- 经典的查找阻塞/等待会话的查询 SELECT l1.sid AS blocking_sid, s1.username AS blocking_user, s1.machine AS blocking_machine, s1.program AS blocking_program, l2.sid AS waiting_sid, s2.username AS waiting_user, l1.type AS lock_type, l1.id1, l1.id2 FROM v$lock l1, v$lock l2, v$session s1, v$session s2 WHERE l1.block = 1 AND l2.request > 0 AND l1.id1 = l2.id1 AND l1.id2 = l2.id2 AND l1.sid = s1.sid AND l2.sid = s2.sid;这个查询能直接显示出谁(
blocking_sid)阻塞了谁(waiting_sid)。 -
使用更现代的视图 (推荐):
-- 查询当前被阻塞的会话及其阻塞者 SELECT sid, serial#, username, event, state, blocking_session, seconds_in_wait FROM v$session WHERE blocking_session_status = 'VALID';BLOCKING_SESSION列会直接显示阻塞它的会话的SID。 -
获取阻塞会话的详细信息:一旦找到阻塞者的SID,就可以查询它在做什么。
SELECT sid, serial#, username, machine, program, module, action, logon_time, sql_id, prev_sql_id FROM v$session WHERE sid = &blocking_sid; -- 替换为上一步找到的阻塞者SID -- 获取阻塞会话正在执行的SQL文本 SELECT sql_text FROM v$sql WHERE sql_id = '&sql_id_from_previous_query';
解决方案与步骤
解决此错误的流程是:定位阻塞者 -> 沟通或终止 -> 优化应用。
步骤与示例:
-
立即解决(治标):
- 沟通:联系阻塞会话对应的用户或应用所有者,请求其提交(
COMMIT)或回滚(ROLLBACK)事务以释放锁。 - 强制终止:如果无法联系或情况紧急,DBA 可能需要强制终止阻塞会话。
警告:强制 kill 会话会回滚该会话的未提交事务,可能需要一些时间,并且会影响对应的用户。-- 1. 找到阻塞会话的SID和SERIAL# SELECT sid, serial# FROM v$session WHERE sid = &blocking_sid; -- 2. 使用 ALTER SYSTEM KILL SESSION 命令终止它 ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE; -- 例如:ALTER SYSTEM KILL SESSION '123,4567' IMMEDIATE;
- 沟通:联系阻塞会话对应的用户或应用所有者,请求其提交(
-
长期优化(治本):
- 优化应用逻辑:这是最根本的解决方案。确保事务尽可能短小精悍,操作完成后立即提交。避免在事务中让用户进行交互(如“请点击确认”),这会导致事务长时间保持打开状态。
- 使用 SELECT … FOR UPDATE NOWAIT:在应用逻辑中,如果预期可能发生冲突,可以使用
NOWAIT选项。这样在请求行锁时如果发现被锁定,会立即抛出ORA-00054: resource busy and acquire with NOWAIT specified错误,而不是等待。应用程序可以捕获这个错误并执行重试或通知用户等操作。BEGIN SELECT salary INTO l_salary FROM employees WHERE employee_id = 100 FOR UPDATE NOWAIT; -- 如果锁被占用,立即失败 -- ...后续处理 EXCEPTION WHEN OTHERS THEN -- 处理异常,例如重试逻辑 END; - 使用 SELECT … FOR UPDATE WAIT n:指定一个具体的最大等待时间(秒),提供了比立即失败更灵活的控制。
SELECT ... FROM ... FOR UPDATE WAIT 10; -- 最多等10秒
通俗易懂的解释
想象一下你去公共卫生间,只有一个隔间。
- 数据库行:就像是卫生间里的一个隔间。
- 排他锁 (X Lock):就像是从里面锁上的门锁。里面的人(事务)占用了隔间(行)。
- 其他用户(会话):就像是想用卫生间的人。如果隔间被占用了,他们必须在外面排队等待。
- ORA-00099 错误:就相当于物业(Oracle数据库)规定:“排队等待时间不能超过5分钟”。
你已经在门口等了5分多钟,里面的人还没出来。物业管理员过来对你说:“错误!(ORA-00099) 等待‘隔间’资源超时了!你别等了,不然整个走廊都被堵死了。” 然后把你请走了。
这和死锁(ORA-00060)有什么区别?
死锁是更奇葩的情况: imagine 两个隔间A和B。
甲占用了A隔间,但他需要B隔间里的纸。
乙占用了B隔间,但他需要A隔间里的纸。
两个人互相等着对方出来,永远等不到。这时物业会发现这个“死结”,强行把其中一个人(比如甲)拖出来(回滚他的事务),这就是死锁。
所以,解决办法就是:
- 找人:物业(DBA)去看看是谁(
blocking_sid)在隔间里待了那么久还不出来。然后敲门叫他快点(沟通),或者直接用备用钥匙把他弄出来(kill session)。 - 立规矩:告诉大家(优化应用),上卫生间要速战速决,完事赶紧出来(及时提交事务)。或者,如果你看到门口有人等,可以选择不等了,直接去别的楼层看看(使用
FOR UPDATE NOWAIT)。
总结
ORA-00099 是一个资源等待超时错误,是Oracle数据库的一种自我保护机制,防止因资源争用导致会话无限期挂起。其根本原因通常是由未及时提交的长事务导致的行锁争用。
解决思路分为两步:
- 应急处理:通过查询
V$SESSION、V$LOCK等视图快速定位并消除阻塞源(沟通或终止会话)。 - 根本预防:优化应用程序设计,缩短事务长度,及时提交,并根据业务场景合理使用
SELECT ... FOR UPDATE NOWAIT/WAIT n来避免不必要的等待。
理解这个错误有助于DBA和开发者更好地处理数据库并发问题,构建更健壮、高性能的应用系统。
欢迎关注我的公众号《IT小Chen》
1万+

被折叠的 条评论
为什么被折叠?



