news 2026/10/16 4:10:52

达梦数据库阻塞排查:用sqllog还原锁等待现场

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
达梦数据库阻塞排查:用sqllog还原锁等待现场

用达梦数据库跑业务的人,应该都有过这种经历:业务群里突然有人喊“系统卡死了”,登录数据库一看,V$LOCK里躺着好几条记录,某个会话的锁一直被另一个会话占着。常规操作是查系统视图,找到持锁会话,看它的用户名、客户端IP、当前SQL。可问题往往没这么简单——持锁会话的SQL_TEXT经常是空的,会话状态看起来也很正常,你根本不知道它半小时前到底执行了哪条 UPDATE,更没法向开发解释为什么事务迟迟不提交。

这篇文章不扯大理论,我以实际排查达梦阻塞会话为背景,讲一讲怎么把sqllog(SQL日志)这个平时容易被忽略的文件用起来,配合系统视图还原一条完整的锁等待现场。适合数据库运维、应用开发,以及第一次上手达梦的人读。读完你至少能回答三个问题:阻塞是怎么产生的、sqllog 里到底藏着什么证据、怎么用这份证据去跟开发对线。

1. 先搞清楚:阻塞会话、锁等待、死锁到底是什么关系

1.1 会话、事务与锁的底层逻辑

数据库里的锁,说白了就是一套“排队机制”。你可以把它想象成线下办事窗口的号码纸:一个窗口只处理一个号,一个人没办完,下一个人只能等着。数据库也一样,一行数据被一个事务修改了,在COMMIT或者ROLLBACK之前,这把写锁不会释放。这个时候,其他会话想要修改同一行数据,就会进入等待状态,表现就是业务卡住、接口超时、页面转圈。

这里有个关键认知:锁本身不是问题,锁被攥着不放才是问题。很多开发有个误解,觉得 SQL 执行完了锁就释放了。不对,SQL 执行完毕只是语句层面的结束,锁要等事务提交或者回滚才真正释放。所以最典型的阻塞场景不是“一条 UPDATE 跑了很久”,而是“一条 UPDATE 瞬间执行完,但事务一直不提交,锁挂了一个小时”。后面介绍的日志分析方法,核心就是抓这种“不提交”的证据。

达梦的阻塞会话,严格来说在系统里没有一个字段叫“阻塞会话”。DBA 口语里说的阻塞会话,通常包含两类:一类是持锁会话,也就是让别人等的那一方;另一类是等待会话,也就是被卡住的那一方。排查时必须同时锁定两端,只看一头很容易误判。

1.2 系统视图解决不了的一个空白:sqllog 的价值

系统视图有个天然短板:它只能反映“当前瞬间”的状态。V$SESSIONS里的SQL_TEXT,显示的往往是这个会话最近一次执行完的 SQL,甚至可能是空的。比如一个持锁会话,它早就执行完 UPDATE 了,现在处于空闲状态,你从系统视图里根本看不出它刚才动了哪张表、改了哪行数据。

sqllog 就不一样。它记录的是 SQL 执行的流水账,每个会话在什么时间点执行了哪条 SQL、执行耗时多少、有没有提交动作,都按时间顺序写在日志文件里。把系统视图查出来的会话 ID 当成线索,去 sqllog 里反查这个会话的历史操作,就能把“现场”还原成一条完整的时间线。

用一句话概括我的心法:系统视图告诉你“现在谁在堵车”,sqllog 告诉你“这辆车是什么时候开进来的、中间停在哪”。两者缺一不可。

1.3 死锁和阻塞,别再混为一谈

写代码的人经常把“数据库卡死”笼统叫死锁,但数据库层面阻塞和死锁是两个完全不同的概念,排查方向也完全不同。我整理了一个简单的对比:

对比项阻塞(锁等待)死锁
表现形式单向等待,A等B环形等待,A等B、B等A
是否自动解除一般不自动解除达梦会检测并自动回滚其中一个事务
日志表现sqllog 里出现长事务、长时间无提交实例日志(dm 开头的日志)里出现 DEADLOCK 相关报错
处理方式需要 DBA 人工介入,如杀掉持锁会话数据库自动处理,但要关注应用报错和业务重试

达梦默认有一个死锁检测机制,相关参数叫DEADLOCK_CHECK_INTERVAL,隔一段时间扫一次,发现死锁就回滚其中一个事务。所以死锁通常表现为“突然报错”,而不是“一直卡住”。如果你看到的是业务长时间无响应、连接堆积,那大概率是阻塞;如果看到的是应用频繁报“死锁”相关错误,那就去翻实例日志,而不是盯着 sqllog 找证据。

这个区分很重要,因为很多人在 sqllog 里翻半天找不到死锁记录,白白浪费时间。

2. sqllog 日志准备:从开启到看懂每一行

2.1 先确认你的 sqllog 到底开没开

达梦默认不一定开启 SQL 日志。很多环境装完库就没人管日志参数,直到出问题才想起来翻日志,结果发现log目录下只有实例日志,根本没有 SQL 流水。所以排查第一步,永远是确认参数。

可以在达梦管理工具或者命令行执行:

SELECT PARA_NAME, PARA_VALUE, PARA_TYPE FROM V$PARAMETER WHERE PARA_NAME LIKE '%SVR_LOG%';

重点看两个值:SVR_LOG和SVR_LOG_MIN_EXEC_TIME。

如果SVR_LOG是 0,说明当前没有记录 SQL 日志。可以临时开一下:

ALTER SYSTEM SET 'SVR_LOG' = 1;

一般不需要重启,具体是动态参数还是静态参数,可以在改之前用V$PARAMETER里的参数类型确认。实际经验是,排查阻塞会话的时候先把日志打开,等问题处理完了,再根据业务情况决定是保持还是调整阈值,避免日志长期全量记录把磁盘撑爆。

2.2 关键参数怎么配:既要记录,又别把磁盘写爆

生产环境不建议用“全量记录”的方式长期跑,日志量太大,IO 成本也高。比较合理的做法是设置一个阈值,只记录执行时间超过阈值的慢 SQL。下面是达梦 sqllog 相关参数里最常用的几个,我按自己的使用习惯整理成一张表:

参数名作用我的建议
SVR_LOG总开关,0 关闭,1 开启平时开 1,但配合阈值使用
SVR_LOG_MIN_EXEC_TIME只记录执行时间超过该阈值的 SQL,单位毫秒默认可能记全部,生产建议设 1000(即 1 秒)以上
SVR_LOG_FILE_SIZE单个日志文件大小,默认单位 MB建议调到 512 或 1024,太大会卡 IO,太小容易丢历史
SVR_LOG_SWITCH_COUNT日志文件轮转个数建议 10 个以上,保证能覆盖足够长的时间窗口
SVR_LOG_ASYNC_FLUSH是否异步刷盘,1 为异步排查期想追求低延迟分析可以开,但故障极端的断电场景可能丢末尾日志

单位描述在不同小版本里可能有差异,最稳妥的办法是执行上面那条查询看默认值。我踩过的一个坑是,某个环境SVR_LOG_FILE_SIZE默认只有 16MB,高峰期几分钟就轮转掉一批,等出问题去翻日志,前一天的 SQL 早就被清了。后来直接把文件大小调大、轮转数量也调大,才保证“翻旧账”的时候有据可查。

2.3 日志文件去哪找,日志里看到的到底是什么

开启日志后,SQL 日志文件默认生成在实例的 log 目录下,一般是<DM_HOME>/data/<实例名>/log或者<DM_HOME>/log,文件名形如dmsql_<实例名>_<日期>.log。不同版本细节可能有差异,你可以在实例目录下执行:

ls -l dmsql_*.log

看到一批以dmsql_开头、带日期的文件,就是它了。

日志里的每一行,通常包含这几类关键信息:SQL 开始执行的时间、会话 ID、用户名、事务 ID、SQL 文本、执行耗时。不同版本字段排列不完全一样,但核心信息是稳定的。我习惯把它理解成数据库的“行车记录仪”:每一条 SQL 都是一个时间戳上的动作,把某个会话 ID 的所有动作按时间串起来,你就能看到它从连接、执行、到沉默的完整过程。

有一点需要注意:sqllog 记录的是“服务端开始执行 SQL”的动作。不是说会话发起了一次 SQL 就一定会留下一行完整记录,如果某个 SQL 在等待锁、始终没有被数据库真正执行,它可能不会出现在日志里。这个细节后面实战部分会再强调。

3. 实战:用 sqllog 反查一个阻塞会话的完整过程

3.1 靠系统视图锁定“两端”:谁持有锁、谁在等待

拿一个我处理过的典型场景举例。某公司的订单系统突然反馈“提交单据一直转圈”,DBA 登录数据库,第一步查锁:

SELECT SESS_ID, USER_NAME, TABLE_NAME, ROW_ID, L_MODE, BLOCKED FROM V$LOCK;

结果里有两条记录很显眼:一条是会话80810持有某张订单表的行锁,另一条是会话90919正在等待同一行数据。为了确认两个会话的基本信息,再查:

SELECT SESS_ID, USER_NAME, STATE, SQL_TEXT, CLNT_IP, CREATE_TIME FROM V$SESSIONS WHERE SESS_ID IN (80810, 90919);

这时候注意看SQL_TEXT字段。别指望它能直接告诉你答案——80810的SQL_TEXT大概率不是空就是一条无关紧要的 SQL,因为它早就执行完了,现在只是在“挂机”。90919的SQL_TEXT很可能是一条 UPDATE 或者 INSERT,但它同样没有执行完,只是卡在锁等待上。系统视图能告诉我们的信息就到这里:两端身份确认了,谁卡谁明确了,但“80810 的锁从哪来、事务从什么时候开始”还是一片空白。

3.2 用 sqllog 还原会话时间线

拿到会话 ID 后,去 sqllog 目录里按会话 ID 过滤。假设当天的日志文件是dmsql_DM_20250115.log,执行:

grep "80810" dmsql_DM_20250115.log > sess_80810.log grep "90919" dmsql_DM_20250115.log > sess_90919.log

如果日志文件很大,可以再加个时间前缀缩小范围,避免把整个文件都导出来:

grep -E "12:0[0-5]:" dmsql_DM_20250115.log | grep "80810"

打开sess_80810.log,你会看到类似这样的时间线(字段格式以你环境实际为准):

12:00:50 SESS=80810 SELECT * FROM t_order WHERE order_id='A001' 12:01:02 SESS=80810 UPDATE t_order SET status='2' WHERE order_id='A001' 12:01:06 SESS=80810 SELECT ... (某条无关查询)

注意:从12:01:06之后,这个会话再也没有新的 SQL 记录,也没有看到 COMMIT 或者 ROLLBACK。这说明什么?说明这个会话把一条 UPDATE 执行完之后,事务一直悬在那里,既不提交也不回滚。

再打开sess_90919.log,你会发现从12:01:30开始,这个会话出现了一条 UPDATE,但之后同样没有输出成功完成的迹象。这里要特别注意:被阻塞的那条 SQL 不一定会在 sqllog 里有完整的“执行完成”记录,因为它根本没跑完,只是在等锁。日志里可能只有它开始进入等待的信息,也可能什么都没记。所以别因为日志里没看到这条 SQL 就怀疑自己的判断。

3.3 从日志到事务:坐实“长事务未提交”

sqllog 的时间线已经给出了很强的暗示,但为了跟开发对线时更有底气,最好再补一个证据:事务状态。用刚才查到的会话 ID 去查事务视图:

SELECT SESS_ID, TRX_ID, STATUS, BEGIN_TIME FROM V$TRX WHERE SESS_ID IN (80810, 90919);

正常情况你会看到80810对应的事务状态是ACTIVE,BEGIN_TIME指向12:01:02左右,也就是它执行 UPDATE 的时间。这就和 sqllog 完美对上了:会话80810在12:01:02执行了一条 UPDATE,开启了事务,然后一直不提交。锁就这么从12:01:02一路挂到现在。

到了这一步,问题根因已经清清楚楚:长事务未提交导致锁长期不释放。接下来要做的事,是带着这组证据去找开发确认业务逻辑。我习惯把证据整理一下再沟通,因为空口说“有个会话锁表了”没人信,但你甩出一张时间线,告诉他哪一秒开始、哪条 SQL、哪个事务、锁了哪一行,开发基本无话可说。

3.4 把证据整理成一条时间线

整理时我会用类似下面这样的格式:

12:00:50 80810 执行 SELECT,正常 12:01:02 80810 执行 UPDATE t_order SET status='2' WHERE order_id='A001' 12:01:06 80810 执行其他查询后无后续动作,事务未提交 12:01:30 90919 尝试 UPDATE 同一行,被阻塞 12:05:00 大量业务会话开始堆积,页面超时

这条时间线一贴出来,谁都看得懂。这也是 sqllog 在阻塞排查中最大的价值:它把数据库内部“谁先谁后”的因果关系,变成人类可读的叙事。

4. 定位之后怎么办:解除阻塞与根治锁等待

4.1 救火操作:kill 和强制释放锁到底怎么选

找到持锁会话后,接下来就是救火。但救火之前一定要确认一件事:这个持锁会话的事务是不是可以放弃。如果它执行的是重要业务操作,直接 kill 可能导致部分数据需要人工补偿。

达梦提供了两种常见手段。第一种是强制释放锁,针对性强一点:

SELECT TRX_ID FROM V$TRX WHERE SESS_ID = 80810; CALL SP_DROP_LOCK(80810, TRX_ID);

SP_DROP_LOCK会把指定会话持有的锁强制释放掉,相当于把那个卡的交易强制回滚,腾出锁资源。这种方式对“只想解锁、不想把所有连接都干掉”的场景更友好。

第二种是直接杀会话:

ALTER SYSTEM KILL SESSION '80810';

或者用达梦的存储过程SP_CLOSE_SESSION(80810)。杀会话的副作用是会断开整个数据库连接,客户端连接池会感知到异常并重建连接。

我的建议是:先跟业务方确认,这个持锁会话所对应的交易是不是已经“死”了。如果确定是超时卡死、不可能再提交,再执行释放操作。执行之后要回到V$LOCK再看一眼锁是否已经解开、等待会话是否恢复正常。

4.2 根因排查:锁为什么会被攥着不放

锁解开了,业务恢复了,但这事还没完。不找根因,同样的阻塞一周后还会再来。

我见过最多的一类根因,是应用的事务边界没控制好。某段业务逻辑是这样的:

  1. 开启事务;
  2. 执行 UPDATE 更新订单状态;
  3. 调用外部接口或者 RPC 等远程服务,等响应;
  4. 返回后继续更新、提交。

问题就出在第 3 步。外部服务慢,事务就只能一直开着,锁也就一直挂到天荒地老。正确做法是把外部调用移出事务,只在事务里保留数据库操作,事务边界越小越好。

第二类常见根因是批量任务没有分批。一个批处理循环里要对几万行数据 UPDATE,代码却写成了一个大事务,全部执行完才提交。中间任何一个环节慢下来,整个事务都拖着锁不放。解决思路是拆批,比如每 500 行或 1000 行提交一次,锁的持有时间从“小时级”降到“秒级”。

第三类根因是 SQL 本身没走索引。UPDATE ... WHERE的条件列没有索引,数据库只能全表扫,扫描过程中会对大量行加锁,锁的范围就从“一行”变成“一片”,阻塞概率指数级上升。用EXPLAIN看执行计划,给 WHERE 条件列建合适的索引,往往比优化应用代码见效还快。

第四类是隔离级别设置问题。达梦默认的隔离级别一般是读已提交,这个级别下普通查询不会持有写锁。但如果应用把隔离级别调到了可重复读或者串行化,查询也可能持锁,阻塞范围会明显扩大。排查时可顺手看下应用的连接配置,确认没有把隔离级别设得不合理。

4.3 监控与预防:别等业务喊卡才动手

阻塞会话这种事,最怕“事后诸葛亮”。系统视图的数据是瞬时的,等你去查的时候可能锁已经释放了,现场全没了。所以有条件的话,建议建一个定时采集脚本,每隔几分钟把V$LOCK、V$SESSIONS、V$TRX的关键字段采集到一张历史表里。

脚本逻辑很简单:定时执行 SELECT,把结果写入一张自定义的监控表,再配合一个告警判断——只要发现有会话状态异常、同一把锁持续超过一定时间,就自动告警。这样下次再出现阻塞,你直接在历史表里查“阻塞从几点开始、持续了多久、涉及哪些会话”,根本不用翻日志翻到头大。

脚本本身我在这里不贴完整代码,但核心思路就一句话:在问题发生的过程中留下证据,比事后从日志里拼证据,省力一百倍。

5. 高频问题和排查经验速查

5.1 排查时最容易踩的六个坑

做了这么多年达梦运维,我在阻塞排查上踩过的坑不少,挑几个典型的列出来,你们遇到类似情况少走弯路。

问题现象正确姿势
sqllog 没开启日志目录里没有dmsql_*.log先查SVR_LOG,确认开启状态
日志文件太小想翻一个小时前的记录,发现文件已经被轮转掉调大SVR_LOG_FILE_SIZE和SVR_LOG_SWITCH_COUNT
被阻塞的 SQL 没日志sqllog 里找不到等待会话的执行记录以系统视图为主、sqllog 为辅,交叉判断
会话 ID 被误认连接池复用连接,同一个 SESS_ID 在不同时间代表不同操作结合时间窗口和客户端 IP/账号一起看
混淆死锁和阻塞业务报“卡死”,但日志里只有 DEADLOCK 报错死锁去实例日志找关键字,阻塞才去 sqllog 找长事务
只盯一条 SQL某条 UPDATE 本身执行很快,但事务不提交看事务提交动作,SQL 执行完不等于锁释放

5.2 一套顺手的小技巧

技术之外,积累几个实操技巧能让排查效率高很多。

第一个技巧,先拍快照再处理。不管多着急,动手 kill 之前,一定先执行三条查询:V$LOCK、V$SESSIONS、V$TRX,把结果保存到文本或者临时表里。等锁解开了,会话断了,这些现场数据就是唯一的历史证据。很多次我都是靠着一张匆忙拍下的快照,事后才分析出真正的根因。

第二个技巧,用 grep 缩小范围,不要直接开整个日志文件。当天日志可能几百 MB,编辑器根本打不开。先grep会话 ID,再grep时间窗口,最后再看内容。一套组合下来,即使 1GB 的日志也能在几秒内过滤出关键信息。

第三个技巧,在 SQL 里加业务注释。有条件的话,让开发在每条 SQL 前面加/* app=订单模块 */这样的注释。注释会出现在 sqllog 里,以后定位业务模块就方便了。虽然这是应用层的事,但 DBA 推动一下效果很好。

5.3 “执行慢”和“持锁久”不是一回事

这个坑太常见了,单独拿出来说。看到 sqllog 里某条 UPDATE 执行耗时 10 秒,很多人会下意识认为“这条 SQL 慢导致锁没释放”。但 SQL 执行时间和锁的持有时间根本不是同一个概念。

执行耗时 10 秒,说明这条 SQL 从开始到结束用了 10 秒;锁的持有时间是从事务开始到事务提交/回滚之间的整段时长。哪怕 SQL 只用了 10 毫秒就跑完了,只要事务挂着不提交,锁照样能持有 10 分钟、10 小时。

所以判断锁问题时,盯的不是“这条 SQL 跑了多久”,而是“这个事务从什么时候开始、什么时候结束”。sqllog 的作用恰恰在于,它能让你看到事务“开始”的时间点,然后通过后续有没有 COMMIT 来判断“结束”的时间点。中间这段空白,就是锁的真实持有窗口。

6. 写在最后:我自己的一点实际体会

说实话,我自己刚接触达梦阻塞排查的时候,也是一个劲地盯系统视图,翻会话、看 IP、猜业务,效率低还容易被开发反问“你怎么证明是这个会话?”后来开始重视 sqllog,养成“以日志时间线说话”的习惯,整个排查节奏才顺起来。

现在我的标准动作已经固定了:先查锁和会话,拍快照;再 sqllog 按会话 ID 过滤时间线;然后查事务状态交叉确认;最后带着完整时间线找开发对根因。这套流程下来,大多数阻塞五分钟内能定位,剩下的大部分时间是等开发确认业务能不能回滚。

最后再分享一个长期建议:sqllog 这种文件平时不起眼,但保留策略一定要做好。你可以不追求全量记录,但至少要保证出问题时能往回翻一两个小时。等真到了需要翻旧账的那一天,你会感谢自己当时多留了几天日志。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 4:10:07

克隆选择免疫算法求解配送中心选址:Python实现与调参复盘

在物流网络设计里&#xff0c;配送中心选在哪&#xff0c;直接决定了运输成本、时效和网络的整体韧性。可一旦候选点数量超过几十个、需求点到了几百个&#xff0c;人工拍脑袋和简单的重心法就完全不够用了。我最近花了一周时间&#xff0c;把一个在免疫学里非常经典的“克隆选…

作者头像 李华
网站建设 2026/10/16 4:09:52

数据库入门指南:从文件存储到SQL、索引与事务的核心原理

1. 先从最朴素的问题说起&#xff1a;为什么不能直接用文件存数据每次给刚入行的同事讲数据库&#xff0c;我从来不急着列概念&#xff0c;而是先让他们回答一个问题&#xff1a;如果只是"存数据"&#xff0c;用一个文本文件甚至一张Excel表不就行了吗&#xff1f;很…

作者头像 李华
网站建设 2026/10/16 4:09:41

Kubernetes CronJob 实战:原理、参数与 MySQL 备份踩坑指南

不少团队把业务服务搬进 Kubernetes 集群之后&#xff0c;会发现还有一类遗留工作赖在老地方&#xff1a;定时任务。备份脚本、报表生成、数据同步这类周期批处理&#xff0c;往往还躺在某台节点的 crontab 里&#xff0c;而 Kubernetes 明明提供了原生的 CronJob 定时任务调度…

作者头像 李华
网站建设 2026/10/16 4:08:45

SIP封装演进与命名规则全解析:从MCM到异构集成

开头干封装的同行聚在一起&#xff0c;聊到SIP&#xff08;System in Package&#xff0c;系统级封装&#xff09;的时候&#xff0c;话题总会滑向两个方向&#xff1a;一是这东西到底算不算先进封装的老祖宗&#xff0c;二是那串绕口的命名到底怎么读、怎么记、怎么不踩坑。这…

作者头像 李华
网站建设 2026/10/16 4:08:44

Python 3.2.2下载与离线环境复现指南

简介&#xff1a;本资源提供Python 3.2.2官方Windows安装包及配套说明文档&#xff0c;面向编程初学者、教学实践者及需兼容旧项目环境的开发者&#xff0c;解决特定历史版本Python的离线获取与快速部署需求。压缩包为ZIP格式&#xff0c;共2个文件&#xff1a;16.72MB的python…

作者头像 李华
网站建设 2026/10/16 4:06:51

Windows内核通信实战:DeviceIoControl与IOCTL缓冲机制详解

1. 为什么DeviceIoControl是内核与用户态通信的首选方案做过Windows内核驱动开发的人都有一个共识&#xff1a;驱动写出来容易&#xff0c;但让用户态程序能稳定、安全地跟驱动对话&#xff0c;才是真正考验功底的地方。DeviceIoControl这个API&#xff0c;就是解决这个问题的核…

作者头像 李华