DB2性能排查:ACTIVE SESSION飙升与临时表空间LATCH等待
简介:这份文档面向DB2数据库运维DBA与性能调优学习者,记录某银行DB2系统SQL执行变慢的真实排查案例,核心聚焦系统临时表空间TEMPSPACE1异常膨胀至10GB所引发的性能问题。资源包共1个doc文件,约607KB,内容涵盖性能问题初步确认、LATCH分析方法、STACK收集与分析技巧,以及用db2trc工具暂停实例抓取诊断信息等实操手段,并给出SQL优化与参数调整的解决思路。已有5903人学习下载,适合希望积累真实故障定位经验、补充临时表空间与并发竞争排查方法的中高级DBA参考借鉴。
1. 系统突然变慢:ACTIVE SESSION 冲到 50,常规排查全部扑空
业务侧反馈“系统变慢”往往是最难接的一类工单,因为这句话背后可能是应用、网络、存储,也可能是数据库。这次遇到的场景很典型:某业务系统从某天开始陆续有人反映响应变慢,数据库监控频繁捕捉到 ACTIVE SESSION 数量超过 50,而此前这个指标长期在 10 以内。登录服务器一看,多条 SQL 执行时间从毫秒级涨到 3~6 秒,对 OLTP 系统来说这已经是肉眼可见的卡顿。
按部就班查了一圈:CPU 利用率正常,操作系统内存正常,缓冲池命中率正常,没有 LOCK WAIT,db2top 右下角的物理读、直接读、物理写、直接写四个时间指标也都正常,说明 I/O 没出问题。抓出执行时间长的 SQL,建索引解决了几个全表扫描,剩下的长 SQL 逻辑复杂但并不是新上线的。折腾一圈,指标全绿,问题还在。真正的原因藏在一个几乎没人看的对象上:系统临时表空间 TEMPSPACE1 涨到了 10G。这篇笔记就把这条从“指标全正常”到“定位到临时表空间”的排查链路拆开讲清楚,适合正在被 DB2 性能问题卡住、又不想盲目改参数的 DBA 和开发同学。
2. 先确认是不是数据库的锅:ACTIVE SESSION 与等待时间的判读
2.1 ACTIVE SESSION 高不等于数据库有问题,但状态分布会说话
ACTIVE SESSION 指的是当前正在处理 SQL 请求的会话数,编译中、执行中、锁等待、提交中、WLM 排队中的会话都计入,而事务里执行完一条语句、下一条还没开始的 UOW Waiting 状态不计入。所以看到数字高,第一步不是慌,而是看这些会话卡在什么状态。用 db2top 连上库后按d看整体,再按l(小写 L)进 SESSIONS 页面看明细。这次所有活动会话都处于 UOW Executing,说明它们是真的在执行,不是排队也不是等锁。
判断逻辑很直接:OLTP 系统平时 ACTIVE SESSION 个位数,突然频繁冲到几十,只有两种可能——业务量真的涨了,或者单条 SQL 变慢了。和业务负责人确认访问量没有增加,那就锁定为后者。这一步的价值在于排除方向性错误,避免在“扩容”和“优化 SQL”之间反复摇摆。
2.2 用 MON_CURRENT_SQL 抓现场,用 MON_GET_PKG_CACHE_STMT 看时间花在哪
抓正在执行的语句,MON_CURRENT_SQL管理视图最直接,动态静态 SQL 都能看到,按已运行时间倒序排:
-- 抓当前正在执行的 SQL,按已运行秒数倒序 SELECT COORD_MEMBER AS MEMBER, VARCHAR(SESSION_AUTH_ID, 10) AS USER, VARCHAR(CLIENT_APPLNAME, 10) AS APPNAME, ELAPSED_TIME_SEC AS ELAPSEDTIME, VARCHAR(ACTIVITY_STATE, 10) AS STATE, TOTAL_CPU_TIME, ROWS_READ, ROWS_RETURNED, QUERY_COST_ESTIMATE, DIRECT_READS, DIRECT_WRITES, VARCHAR(STMT_TEXT, 2000) AS STMT_TEXT FROM SYSIBMADM.MON_CURRENT_SQL ORDER BY ELAPSED_TIME_SEC DESC;ELAPSED_TIME_SEC是语句已运行秒数,ACTIVITY_STATE能看出是执行还是等锁。注意这个视图抓的是“此刻”,语句没跑完时显示的时间比完整执行时间短,所以要在 ACTIVE SESSION 高的时候多执行几次才能抓到现场。
要看时间到底消耗在哪,用MON_GET_PKG_CACHE_STMT表函数算各项等待占比:
-- 计算平均执行时间与各类等待时间占比,定位瓶颈类型 SELECT MEMBER, NUM_EXECUTIONS, STMT_EXEC_TIME, DECIMAL((TOTAL_ACT_WAIT_TIME / DOUBLE(STMT_EXEC_TIME)) * 100, 5, 2) AS pct_wait, DECIMAL((POOL_READ_TIME / DOUBLE(STMT_EXEC_TIME)) * 100, 5, 2) AS pct_read, DECIMAL((LOCK_WAIT_TIME / DOUBLE(STMT_EXEC_TIME)) * 100, 5, 2) AS pct_lock, DECIMAL((LOG_DISK_WAIT_TIME / DOUBLE(STMT_EXEC_TIME)) * 100, 5, 2) AS pct_log, DECIMAL((TOTAL_SECTION_SORT_TIME / DOUBLE(STMT_EXEC_TIME)) * 100, 5, 2) AS pct_sort, CAST(FLOAT(STMT_EXEC_TIME) / FLOAT(NUM_EXECUTIONS) AS DECIMAL(10,3)) AS AVG_EXEC_TIME, VARCHAR(STMT_TEXT, 500) AS STMT_TEXT FROM TABLE(MON_GET_PKG_CACHE_STMT(NULL, NULL, NULL, -2)) WHERE NUM_EXECUTIONS > 1 AND STMT_EXEC_TIME > 0;-2表示取所有成员上的语句信息,NUM_EXECUTIONS > 1过滤掉只跑过一次的语句,避免平均值失真。结果里几条以 with 开头的语句pct_wait超过 95%,但读、锁、日志、排序占比都很低,说明时间花在了一种不在这几类里的等待上。把TOTAL_EXTENDED_LATCH_WAIT_TIME加进查询字段,答案就出来了:它们在等 LATCH。
提示:如果应用是拼接 SQL 而不是变量绑定,包缓存里几乎每条语句都是新条目,
NUM_EXECUTIONS普遍很小,这时候看平均值意义有限,要结合MON_CURRENT_SQL的现场抓取一起判断。
3. LATCH 等待怎么查:db2pd 抓不到就持续抓,再配合 STACK 看 EDU 在干什么
3.1 pool_table_latch 异常:一个 Holder 卡住 23 个 Waiter
LATCH 是 DB2 内部为保证多线程访问共享内存结构一致性而设的轻量锁,和用户可见的行锁、表锁不是一回事,文档里几乎没有面向用户的说明。第一次用db2pd -latches快速扫了一眼没发现异常,问题在于 LATCH 等待只持续几秒就消失,单次采样很容易错过。改成持续采样:
# 每 2 秒采样一次 LATCH 情况,持续输出到屏幕 db2pd -latches -rep 2-rep 2是重复间隔秒数,不加次数就持续输出,Ctrl+C 结束。这次抓到了:EDU 11665 持有pool_table_latch,同时有 23 个 EDU 在等它。一个操作拿着 LATCH 阻塞 23 个操作,ACTIVE SESSION 至少 24,和监控看到的数字对得上。但 LATCH 名字只是内部符号,光看名字不知道 Holder 在干什么,而等待几秒就散了,来不及抓现场。
3.2 用 db2pd -stack 收集 STACK,grep 出 Holder 和 Waiter 的调用栈
db2pd 的-stack能在收集堆栈的同时记录每个 EDU 当时的 LATCH 和 LOCK 信息,正好用来补上“Holder 在干什么”这块拼图:
# 每 3 秒收集一次全实例 STACK,连续 10 次,输出到指定目录 mkdir -p /tmp/stack0531 db2pd -stack all dumpdir=/tmp/stack0531 -rep 3 10 # 在结果里找 pool_table_latch 的持有者和等待者 cd /tmp/stack0531 grep pool_table_latch *stack.txtdumpdir指定输出目录,不指定就落到 DIAGPATH;-rep 3 10表示间隔 3 秒、共 10 轮,因为等待现象不是一直存在,必须持续采样才有命中率。grep 结果里Holding Latch type是持有者,Waiting on latch type是等待者,后面跟着地址、代码行号和源文件名。
注意:生产库繁忙时收集 STACK 会明显消耗资源、影响性能,务必在低峰或问题复现窗口内短时收集,别长时间挂着跑。
3.3 读 STACK 不求看懂全部,抓函数名和调用关系就够
Holder 的栈里能看到sqlbSnapshotTablespace -> sqlbMonSMSGetContainerPages,字面意思是对表空间做快照、在监控 SMS 表空间时获取容器数据页;Waiter 的栈里是sqldTableCreateTemp -> sqldInternalCreateTable -> sqlbGetSysTempDatapool,即创建内部临时表、申请系统临时表空间,再往上有sqldEvalDataPred(谓词求值),说明这是一条带谓词、用到临时表的查询。结论成形:监控代理定期对表空间做快照,快照在 SMS 表空间上取容器页信息耗时变长,长时间持有pool_table_latch,把正在建临时表的查询全堵住了。
手工 grep 效率低,DB2 安装目录下有个 Perl 脚本analyzestack能汇总相同栈并做 LATCH 分析:
# -d 指定比较函数个数,-i 指定 stack 文件目录,-l 做 LATCH 分析 perl ~/sqllib/pd/analyzestack -d 50 -i /tmp/stack0531 -l执行后生成LatchAnalysis.out和StackAnalysis.out,前者列出 Holder/Waiter 配对,后者按调用栈聚类。这个脚本没有官方支持承诺,属于“能用但别指望文档”的工具,参数细节用-help看。
4. 根因落地:TEMPSPACE1 涨到 10G,TDA 文件没被回收
4.1 一个反直觉的信号:查表空间信息本身都要好几秒
顺着“临时表空间”这条线去查,发现db2 LIST TABLESPACES SHOW DETAIL都要跑好几秒才出结果——查元数据都慢,说明表空间对象本身状态异常。输出显示 TEMPSPACE1 是系统管理的 SMS 临时表空间,总页数 354661,页大小 32768 字节,算下来约 10G,而且 Used pages 等于 Total pages。正常系统临时表空间应该很小,涨到 10G 本身就是异常。
到容器目录下看文件,一堆SQL0xxxx.MEMBER0000.TDA文件,单个 1MB 左右,数量很多。TDA 是临时数据文件,SMS 临时表空间里的 TDA 文件在某些情况下不会被及时删除,会一直占着空间,这是产品设计行为而非缺陷。翻监控历史曲线能看到某个时间点 TEMPSPACE1 从接近 0 直接暴涨到 10G,对应的是当时一瞬间 ACTIVE SESSION 冲到 800 多,大量 SQL 同时使用临时表空间做排序和临时表,语句都成功了、库也没崩,但留下一地 TDA 文件,成了后来踩到的雷。
4.2 解决动作:deactivate/activate 数据库,不是重启实例
确认根因后处理很简单,但有个关键区别必须说清楚:只重启实例释放不掉这些临时表空间,必须对数据库做 deactivate 再 activate。
# 先强制断开所有应用连接,再停库、起库 db2 force applications all db2 deactivate db <dbname> db2 activate db <dbname> # 验证临时表空间是否回落 db2 "LIST TABLESPACES SHOW DETAIL" | grep -A 6 TEMPSPACE1force applications all踢掉残留连接,否则 deactivate 会失败;deactivate db才会触发临时表空间的清理,activate db让数据库保持常驻激活状态。处理完再跑LIST TABLESPACES SHOW DETAIL,秒级返回,Used pages 大幅回落。持续观察一段时间,ACTIVE SESSION 回到个位数,之前 3~6 秒的语句恢复正常,性能问题消失。
4.3 为什么临时表空间大会拖慢查询:一条 LATCH 竞争链
把因果串起来:临时表空间膨胀到 10G 后,监控代理对表空间做快照、枚举 SMS 容器页的耗时被拉长,这个操作持有pool_table_latch的时间随之变长;而查询里只要用到系统临时表(排序溢出、内部临时表),就要申请同一个 LATCH 去拿临时数据池,于是被堵在门口。等待时间不计入读、锁、日志、排序任何一类,所以在MON_GET_PKG_CACHE_STMT里表现为“等待占比 95% 但不知道在等什么”。并发一上来,等待链越拉越长,ACTIVE SESSION 自然冲高。这也解释了为什么优化 SQL、建索引都没用——瓶颈不在语句本身,而在一个被忽视的共享资源上。
5. 避坑清单:这类问题最容易踩的五个坑
现象:db2pd -latches单次执行显示一切正常,误判没有 LATCH 问题。原因:LATCH 等待只持续几秒,单次采样命中率极低。 解决:用db2pd -latches -rep 2持续采样,在 ACTIVE SESSION 高的窗口内跑,至少观察几分钟。
现象:重启数据库实例后,TEMPSPACE1 大小没变。原因:实例重启不触发数据库级临时表空间清理。 解决:必须db2 deactivate db再db2 activate db,执行前先force applications all。
现象:看到MON_GET_PKG_CACHE_STMT里等待占比 95%,却查不出等的是什么。原因:默认字段只覆盖读、锁、日志、排序,LATCH 等待不在其中。 解决:把TOTAL_EXTENDED_LATCH_WAIT_TIME加进查询字段,再结合db2pd -latches交叉验证。
现象:生产库上收集 STACK 后业务更慢了。原因:db2pd -stack all会挂起 EDU 抓栈,繁忙库上开销明显。 解决:限定-rep次数、选低峰窗口、只对必要成员收集,收集完立即停止。
现象:只盯着慢 SQL 优化,改完索引问题依旧。原因:慢是结果不是原因,共享资源竞争会让所有用到临时表的语句一起变慢。 解决:先看 ACTIVE SESSION 状态分布和时间占比,确认是语句自身慢还是等资源,再决定优化方向。
6. 快速定位同类问题的排查顺序与验证手法
遇到“指标全绿但系统就是慢”的 DB2 性能问题,我现在的固定顺序是:先看 ACTIVE SESSION 的状态分布,确认是执行中还是等锁排队;再用MON_GET_PKG_CACHE_STMT算时间占比,把TOTAL_EXTENDED_LATCH_WAIT_TIME一起带上,等待占比高但读锁日志排序都低,基本就指向 LATCH;接着db2pd -latches -rep 2持续采样找 Holder 和 Waiter 的 EDU;然后db2pd -stack all dumpdir=... -rep 3 10抓栈,grep LATCH 名,用analyzestack汇总;最后顺着栈里的函数名找被访问的对象,这次就落到了表空间快照和临时表空间上。
验证环节有个很实用的小实验,用db2trc在特定函数入口挂起实例,把“表空间快照持有 pool_table_latch”这件事坐实:
# 终端一:连库后开启跟踪,命中指定函数入口时挂起实例 db2 connect to <dbname> db2trc on -crash DB2.SQLB.sqlbSMSGetContainerPages.entry -suspend # 终端二:触发一次表空间信息查询,观察是否被挂起 db2 "LIST TABLESPACES SHOW DETAIL" # 终端一:恢复运行并关闭跟踪 db2trc resume db2trc off db2trc fmt db2trc.dmp db2trc.txt-crash指定命中即挂起的函数入口,-suspend让实例暂停,这样能从容收集现场;resume恢复、off关闭、fmt把二进制 dump 转成可读文本。这个手法适合在测试环境复现验证,生产上慎用。
几个关键指标的判读可以对照下表:
| 观察项 | 正常表现 | 异常信号 | 下一步 |
|---|---|---|---|
| ACTIVE SESSION | OLTP 个位数到二三十 | 突然频繁冲到 50 以上 | 看状态分布 |
| 等待时间占比 | 读、锁、日志、排序分布合理 | 总等待高但各项都低 | 查 LATCH 等待 |
| TEMPSPACE1 大小 | 很小,接近 0 | 数 GB 且 Used 等于 Total | 查 TDA 文件 |
| LIST TABLESPACES | 秒级返回 | 执行要好几秒 | 表空间对象异常 |
从那以后我每次接手一个库,第一件事就是把 TEMPSPACE1 的大小和 TDA 文件数量加进日常巡检,而不是等业务喊慢才去看。临时表空间这种平时没人关心的对象,出问题时恰恰最难联想。希望这些排查顺序能帮你在下一次“指标全绿”的故障里少绕几圈。
本文还有配套的精品资源,点击获取