在DB2数据库运行过程中,SQLCODE -911是事务处理失败的典型信号之一。它并不表示某一条SQL本身写错,而是当前事务因为锁资源竞争被数据库管理器强制回滚。错误信息里通常会包含 THE CURRENT TRANSACTION HAS BEEN ROLLED BACK DUE TO A DEADLOCK OR TIMEOUT 这样的描述,这里的 deadlock 与 timeout 正好对应两类完全不同的场景。很多团队遇到 -911 后习惯直接调大 LOCKTIMEOUT,但如果问题本质是死锁,调大超时时间反而会让锁竞争持续更久,回滚代价更高。所以第一步要做的不是改参数,而是判断这次回滚到底由死锁引起,还是由锁等待超时引起。

先分清死锁与锁超时:两种 -911 的底层差异
死锁与锁等待超时虽然最终都可能抛给应用同一个错误码,但触发机制完全不同。死锁是至少两个事务之间形成循环等待:事务A持有资源1等待资源2,事务B持有资源2等待资源1,谁都无法继续提交,也没有一方会主动释放锁。DB2有一个死锁检测器,会周期性扫描锁等待图,一旦发现闭合环路,就会选择一个代价较小的事务作为牺牲品进行回滚,被回滚的事务收到 SQLCODE -911,同时错误信息中会明确出现 DEADLOCK 字样。
锁等待超时则没有循环关系。比如事务A更新了一行数据后一直没有提交,事务B尝试更新同一行数据,只能进入等待队列。如果事务A长时间不释放锁,事务B等待时间超过数据库参数 LOCKTIMEOUT 设定的秒数,就会被强制终止并报出 SQLCODE -911。此时错误信息通常包含 TIMEOUT 或 LOCK TIMEOUT,原因码可能是 68 或 00C9008E,具体取决于DB2版本。理解这两者的差异,决定了后续是去解决访问顺序问题,还是去解决长事务和参数配置问题。
下面的SQL可以模拟一个典型的死锁场景。事务A先更新账户1再更新账户2,事务B恰好相反,先更新账户2再更新账户1。如果两个事务同时执行,事务A持有账户1的行锁等待账户2,事务B持有账户2的行锁等待账户1,就会触发DB2的死锁检测。
-- 事务A:先修改账户1,再修改账户2 UPDATE accounts SET balance = balance - 100 WHERE acct_id = 1; UPDATE accounts SET balance = balance + 100 WHERE acct_id = 2; -- 事务B:先修改账户2,再修改账户1 UPDATE accounts SET balance = balance - 50 WHERE acct_id = 2; UPDATE accounts SET balance = balance + 50 WHERE acct_id = 1;
快速定位:利用 db2pd 与锁快照找出竞争资源
当应用日志出现 -911 时,最直接的方法是打开数据库锁快照,观察当前正在等待的锁以及对应的持锁事务。db2pd 是DB2提供的一个轻量级诊断工具,不需要连接数据库就能直接读取内存中的管理器信息。执行 db2pd -db sample -locks wait show detail 可以只列出状态为等待的锁,避免输出过多无关信息。
db2pd -db sample -locks wait show detail
输出中需要重点关注几个字段:TranHdl 表示事务句柄,Lockname 表示锁名称,Mode 表示请求的锁模式,Status 表示当前状态,TbspaceID 和 TableID 用来定位具体的表和表空间。如果看到两个不同事务都在等待对方持有的锁资源,基本可以判定为死锁。如果只有一个事务处于 Lock Waiting 状态,则需要进一步查看持锁事务的应用程序ID和它最后一次执行的SQL语句。
拿到 TbspaceID 和 TableID 后,可以通过系统目录表查询对应的表名,便于分析竞争热点。
SELECT tabschema, tabname FROM syscat.tables WHERE tbspaceid = 2 AND tableid = 18;
对于偶发性死锁,锁快照可能已经看不到了,这时需要靠事件监视器来记录历史信息。创建死锁事件监视器后,DB2会在死锁发生时把参与事务、SQL文本、锁对象等信息写入指定目录。Linux下通常写到 /tmp/dlevents,Windows下可以写 C:\dlevents,注意Windows路径中的反斜杠必须保留。
db2 create event monitor dlmon for deadlocks with details write to file '/tmp/dlevents'
处理与优化:从事务设计、SQL和参数三个层面降低 -911
如果确认是死锁,最有效的办法是统一应用内部多个事务的更新顺序。例如所有涉及账户余额变更的事务都先更新主账户表,再更新明细表;或者所有事务都按照 acct_id 升序处理,就能避免交叉持锁。对于转账场景,可以约定先处理 acct_id 较小的账户,再处理较大的账户。这样即使并发量很高,也不会形成环状等待。
事务持续时间过长也是锁冲突的重要原因。应避免在事务中执行长时间计算、远程调用或等待用户输入。把只读查询放到事务外,把必要的数据修改集中在一起提交。另一个常被忽视的问题是缺失索引导致的锁升级。如果在更新语句的 WHERE 条件列上没有索引,DB2可能扫描大量行并持有过多行锁,当行锁数量超过 MAXLOCKS 参数设定的阈值后,会升级为表锁,进一步扩大竞争范围。为 WHERE 条件列添加合适索引,可以显著减少锁粒度。
-- 为 acct_id 创建索引,避免全表扫描引发锁升级 CREATE INDEX idx_accounts_acct_id ON accounts(acct_id);
隔离级别也会直接影响锁行为。DB2默认使用游标稳定性隔离级别,读取数据时会短暂加共享锁。如果应用对读一致性要求不高,可以在查询中指定未提交读,从而避免读锁与写锁互相阻塞。
-- 使用未提交读隔离级别,减少读锁竞争 SELECT balance FROM accounts WHERE acct_id = 1 WITH UR;
针对锁等待超时,可以适当调整 LOCKTIMEOUT 参数。它表示事务等待锁的最长秒数,设置过小容易误伤正常排队的事务,设置过大又会拖慢整体响应。一般建议设置在 30 到 60 秒之间,配合应用层重试机制使用。
db2 update db cfg for sample using LOCKTIMEOUT 30
应用层也必须对 -911 做好补偿处理。因为该错误表示整个事务已经被回滚,所以不能只重试最后一条语句,而必须从头重新开启事务。下面是一段Java重试逻辑,捕获 SQLCODE -911 后回滚事务并重新执行。
int maxRetries = 3;
int attempts = 0;
while (attempts < maxRetries) {
try {
conn.setAutoCommit(false);
updateAccount(conn, 1, -100);
updateAccount(conn, 2, 100);
conn.commit();
break;
} catch (SQLException e) {
conn.rollback();
if (e.getErrorCode() == -911 && attempts < maxRetries - 1) {
attempts++;
Thread.sleep(200L * attempts);
} else {
throw e;
}
}
}
建立可观测性:用事件监视器与锁快照持续发现潜在锁竞争
处理完当前问题后,还需要建立一套持续监控机制,避免相同的锁竞争反复出现。死锁事件监视器创建后默认不会自动刷新,需要手动执行 flush 命令把缓冲区中的记录写入文件,然后用 db2evmon 工具进行格式化查看。
db2 flush event monitor dlmon db2evmon -db sample -evm dlmon
db2evmon 的输出会包含死锁发生时间、参与事务的应用程序ID、事务启动时间以及每条SQL的完整文本。通过这些信息可以还原死锁发生前的操作序列,找出哪些事务存在访问顺序不一致的问题。如果死锁集中在某几张表,还可以结合锁快照分析这些表上的索引情况和事务持有锁的时间。
对于长时间运行的旧事务,可以使用 MON_GET_UNIT_OF_WORK 表函数查询当前活动事务的开始时间。运行时间过长的事务往往持锁时间也长,是导致锁超时的主要来源。
SELECT application_handle, uow_start_time, workload_occurrence_state FROM TABLE(MON_GET_UNIT_OF_WORK(NULL, -1)) AS t WHERE workload_occurrence_state = 'EXECUTING' ORDER BY uow_start_time ASC;
如果经常发生锁升级,可以检查 MAXLOCKS 和 LOCKLIST 参数。MAXLOCKS 控制单个事务持有锁列表的百分比阈值,超过该值就触发锁升级;LOCKLIST 则决定锁列表可用内存大小。适当增大这两个参数可以降低锁升级频率,但需要结合服务器内存情况调整,不能盲目加大。
db2 update db cfg for sample using MAXLOCKS 20 LOCKLIST 2000
SQLCODE -911 本身并不是不可接受的异常,真正需要关注的是它背后暴露出来的锁竞争模式。通过区分死锁和锁超时、定位竞争对象、优化事务边界和索引设计,再配合合理的参数配置与重试策略,可以将 -911 出现的频率控制在一个较低的水平。关键是让锁持有时间尽可能短,让事务访问顺序尽可能一致,让监控信息尽可能完整。
DB2 SQLCODE -911死锁处理锁超时修改时间:2026-09-20 08:19:53