资讯动态

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

发布时间:2026/10/16 4:10:55 来源:尧图企业网站定制
用达梦数据库跑业务的人应该都有过这种经历业务群里突然有人喊“系统卡死了”登录数据库一看V$LOCK里躺着好几条记录某个会话的锁一直被另一个会话占着。常规操作是查系统视图找到持锁会话看它的用户名、客户端IP、当前SQL。可问题往往没这么简单——持锁会话的SQL_TEXT经常是空的会话状态看起来也很正常你根本不知道它半小时前到底执行了哪条 UPDATE更没法向开发解释为什么事务迟迟不提交。这篇文章不扯大理论我以实际排查达梦阻塞会话为背景讲一讲怎么把sqllogSQL日志这个平时容易被忽略的文件用起来配合系统视图还原一条完整的锁等待现场。适合数据库运维、应用开发以及第一次上手达梦的人读。读完你至少能回答三个问题阻塞是怎么产生的、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 SESS80810 SELECT * FROM t_order WHERE order_idA001 12:01:02 SESS80810 UPDATE t_order SET status2 WHERE order_idA001 12:01:06 SESS80810 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对应的事务状态是ACTIVEBEGIN_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 status2 WHERE order_idA001 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 根因排查锁为什么会被攥着不放锁解开了业务恢复了但这事还没完。不找根因同样的阻塞一周后还会再来。我见过最多的一类根因是应用的事务边界没控制好。某段业务逻辑是这样的开启事务执行 UPDATE 更新订单状态调用外部接口或者 RPC 等远程服务等响应返回后继续更新、提交。问题就出在第 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 这种文件平时不起眼但保留策略一定要做好。你可以不追求全量记录但至少要保证出问题时能往回翻一两个小时。等真到了需要翻旧账的那一天你会感谢自己当时多留了几天日志。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑