注册
锁阻塞问题与解决报告
专栏/技术分享/ 文章详情 /

锁阻塞问题与解决报告

🌊Amaharashi 2026/09/23 324 0 0
摘要

1 概述

锁阻塞是指多个会话并发访问同一数据对象(通常为同一行)时,持有锁的事务长期不提交、不回滚,导致后续请求锁的事务一直等待的系统异常。其典型特征为系统资源指标(CPU/内存/IO/负载)全部正常,但业务会话挂起不响应——因为等待锁的会话不消耗计算资源,仅处于等待状态,故"资源正常"与"业务卡死"并存即是强烈信号。排查遵循"由硬到软"路径:先以 top/vmstat/free 排除硬件层,再以进程检查排除服务层,随后进入数据库内部,通过VSESSIONS定位挂起会话、VLOCK 查看锁明细(BLOCKED=1 为等待者、0 为持锁者)、VTRXWAIT 还原等待链(谁等谁),最终定位持锁事务及其 SQL。根因多为业务长事务未及时提交、热点行高并发竞争;恢复按优先级依次为:持锁事务主动提交/回滚(治本)、SP_CLOSE_SESSION 强制断开持锁会话(治标,生产慎用),并可通过 LOCK_WAIT 设置锁等待超时预防。 阻塞:多个线程之间的相互影响,等待临界资源; 死锁:多个线程之间互相等待,形成等待环; 对于死锁,达梦数据库会自动检测,并选择牺牲掉其中的一个事务,保证其它事务正常运行。 死锁一般是由于应用程序bug导致的,当有修改多表的事务时,应尽量保证每个事务以相同的顺序锁定表。 可以通过VDEADLOCK_HISTORY查询死锁历史,其中记录了产生死锁后被牺牲掉的事务的事务ID、会话ID、执行的SQL语句以及死锁发生时间(没有记录造成死锁的其它事务)。
对于阻塞,达梦数据库不会自动处理,被阻塞的事务会一直挂起,直到获取到所需的资源。
阻塞一般是由应用程序bug造成的,在应用编写中,应尽量形成短事务,快速提交。

2 系统资源诊断

2.1 TOP查看系统进程

top命令查看系统进程状态:
image.png
系统近乎空载,load average 0.00,163个任务只有1个running,162个sleeping
CPU 98.4% idle,内存 1.7G used / 5G buff-cache,Swap 仅 12M。

2.2 CPU细化分析

vmstat 1 5
image.png
无任何瓶颈信号,r=0(运行队列空)、b=0(无阻塞进程)、id=100(CPU 全闲);si/so=0(无换页)、bi/bo≈0(无 IO 压力)、in/cs 极低(中断/上下文切换几乎为零)

2.3 内存检查

free -h
image.png
内存充足,7.9G 总量、available 5.0G,Swap 4G 只用了 12M

2.4 服务进程

image.png
两个dmserver进程都在跑:59827 = DMSERVER(/dm/data/DAMENG/dm.ini,5236 库)、61871 = HRDB(5237 库)

3 数据库状态诊断

3.1 锁阻塞排查

SELECT SESS_ID, CLNT_IP, STATE, TRX_ID FROM V$SESSIONS ORDER BY CREATE_TIME;

行 2:STATE=IDLE 但 TRX_ID=8072。空闲会话意味着没有语句在跑,但手上却挂着一个事务。正常空闲会话应该 TRX_ID 为空或早已提交;带着事务,说明它执行完语句后既没 COMMIT 也没 ROLLBACK,事务一直开着。开着事务=锁不释放=其他会话被卡。
行 3:STATE=ACTIVE + TRX_ID=8075,有一个会话正处于活跃执行态且带着事务。
当发现有锁阻塞时,要进一步排查:
SELECT * FROM VTRXWAIT; ![image.png](https://download.dameng.com/eco-file-server/file/eco/preview/20260921151102YV9ZYUU3I1IW2Y12I3) ID=8075:正在等待锁的事务。WAIT_FOR_ID=8072:它等的是谁——事务 8072(就是上一段那个 IDLE 带事务的会话)。WAIT_TIME=124220:以毫秒计约 124 秒,与 inject.sh 的持锁窗口吻合,说明 8075 已经等了 2 分多钟,属于持续阻塞。两个 LOCK 地址字段:显示等待者请求的是哪把锁。 SELECT TRX_ID, TABLE_ID, ROW_IDX, LMODE, BLOCKED FROM VLOCK;
image.png
LMODE 的语义:X=排他锁(写锁)、IX=意向排他锁、IS=意向共享锁(SELECT 产生, 8066 的那些 IS 就是 SELECT 查出来的)。UPDATE 一条记录需要"表级 IX + 行级 X"两把锁。8072 对 (-1,-1) 的 X 锁:达梦用 TABLE_ID=-1, ROW_IDX=-1 表示"未提交事务修改的行锁"(事务缓冲区里的脏行)。意思就是:8072 改的那行没提交。8075 对 (1013,1) 的 X 锁,BLOCKED=1:它对 FAULT_ACCOUNT 第 1 行请求 X 锁,被卡住。BLOCKED 字段就是"谁持谁等"的分界线:0=锁已到手(持锁者),1=锁在排队(等待者)。其余 TRX_ID=0 的 IX 锁是系统内务(对系统表 DML 的锁),TRX_ID=8066 的 IS 是看它们的 SELECT。
确认锁等待后,通过系统元数据表 SYSOBJECTS 将 VLOCK 中的 TABLE_ID 翻译为表名: SELECT ID, NAME, SUBTYPE$ FROM SYSOBJECTS WHERE ID = 1013;
image.png
ID=1013,NAME=FAULT_ACCOUNT,SUBTYPE=UTAB(用户表)。结合VLOCK 中 ROW_IDX=1,可确认锁落在 FAULT_ACCOUNT 表第 1 行(ACCT_ID=1 的记录)上。
排查过程中再次查询持锁事务与等待事务时返回"未选定行"——8072、8075 两个事务均已不存在,说明持锁窗口已结束,故障自动恢复。恢复验证:
SELECT COUNT(*) AS STILL_WAITING FROM V$TRXWAIT;
image.png
期望返回 0,即锁已全部释放。
为什么故障自动恢复了?
真实的锁阻塞不会自愈,锁的释放只有一个条件——持锁事务结束。本次演练持锁窗口由 inject.sh 人为设定为 180 秒,时间一到:持锁会话的 disql 进程读到管道 EOF 后退出 → 会话断开 → 未提交事务被强制回滚 → X 锁释放 → 等待会话 8075 立即获得锁并继续执行。持锁者会话生命周期结束,数据库将未提交事务连同持有的锁一并清理,并非数据库主动判断放锁。

4 小结

什么情况下会形成阻塞?
达梦数据库默认采用读提交(Read Committed)隔离机制,具备以下特点:
查询永不阻塞:普通查询(SELECT)不会因其他事务持锁而被阻塞;
查询一般不阻塞增删改:普通查询不会阻塞 DML 操作,SELECT FOR UPDATE 除外;
INSERT 被阻塞的场景:多个事务同时向带主键或唯一约束的表插入相同数据时,后到的事务需等待先到事务提交或回滚后才能判断约束是否冲突;
UPDATE / DELETE 被阻塞的场景:待操作的数据已被其他事务修改且一直未提交或回滚时,后续事务对该数据的修改请求会被阻塞。
阻塞与死锁的解决方法。
死锁:无需人工干预,数据库系统可自动检测并解除;但死锁属于非正常情况,需追查死锁原因,从应用逻辑层解决(如统一加锁顺序、避免交叉占用资源)。
阻塞:多数阻塞源于业务应用提交事务过慢,最终事务仍会提交或回滚,因此一般情况下会自动消除。若阻塞长时间未消除,可在数据库层执行 SP_CLOSE_SESSION(会话ID);强制终止阻塞源头的会话、触发其事务回滚来解除阻塞。该操作会导致阻塞方未提交数据回滚,须经用户确认后方可执行,不得私自操作。
怎么预防锁阻塞?
业务层要求短事务、尽早提交,避免长事务长期持锁;
应用连接池合理设置,应用异常时及时释放连接,防止"连接存活但事务悬挂";
避免热点行竞争(多业务并发更新同一记录),各事务 UPDATE 加锁顺序保持一致;
设置 LOCK_WAIT 有限超时(如 30~60 秒),使异常锁等待自动暴露,避免无限挂起;
定期巡检 VTRXWAIT/VTRXWAIT / VLOCK,锁等待超阈值时自动告警。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服