☰
DB2性能问题剖析:TEMPSPACE1临时表空间引发ACTIVE SESSION飙升的排查与解决
2026/10/17 14:51:22 网站建设 项目流程

简介:一份面向 DB2 数据库管理员与性能优化人员的实战案例分析文档,来自某银行生产环境真实故障:SQL 执行明显变慢、ACTIVE SESSION 异常升高,常规 CPU、内存、I/O、锁等待检查均未发现问题,最终定位为系统临时表空间 TEMPSPACE1 膨胀至 10GB 所致。资源围绕问题定位、原因剖析与解决思路展开,详细讲解 db2top 会话监控、LATCH 竞争分析、堆栈收集以及 db2trc 暂停实例等诊断手段,并给出优化 SQL、调整参数、清理临时对象等处置建议。文档以 1 个 doc 文件呈现,压缩包约 607KB,内容精炼、步骤完整,适合需要排查 DB2 临时表空间性能问题的中高级 DBA 参考。已有 5899 人学习下载。

1. ACTIVE SESSION 飙到 50 的那一刻:一次被 TEMPSPACE1 坑惨的 DB2 性能问题

DB2 系统临时表空间过大引发的性能问题,听起来像冷门话题,真正碰上能把人折腾到怀疑人生。某银行核心系统某天开始频繁报慢,监控里 ACTIVE SESSION 数量突破 50,多条 SQL 执行时间从毫秒级拉到好几秒。按常规套路检查 CPU、内存、I/O、锁等待、缓冲池命中率,全部正常;慢 SQL 也抓了,索引也建了,长语句也优化了,性能问题纹丝不动。最后发现元凶竟是平时没人注意的 TEMPSPACE1——系统临时表空间涨到 10G,遗留的 TDA 文件让表空间快照慢到离谱,间接把查询全部堵死。本文复盘完整定位过程、踩过的坑、以及最终用 deactivate/activate 收场的方案,对正在排查 DB2 性能问题的 DBA 有参考价值。

2. 现象确认与快速排除法:常规指标全部正常,问题到底在哪

第一次收到业务反馈的时候,我其实抱着一丝侥幸心理——业务人员的感知有时候是主观的,也许只是某个应用版本发布后的偶发波动。但当数据库监控开始频繁捕捉到 ACTIVE SESSION 数量达到 50 以上的时候,这个问题就不能再拖了。作为运维 DBA,看到这个指标的那一刻,基本可以断定:数据库自身出问题了,且 SQL 执行时间在变长。

2.1 ACTIVE SESSION 高不代表一切,状态分布才是关键

在 DB2 的监控体系里,ACTIVE SESSION 数量(即 db2top 中的 ActSess 指标)是最直观的负载信号。它统计的是数据库当前正在执行操作的会话数,也就是正在处理请求的 application 数量。用 db2top 连上数据库之后,按 d 进入数据库概览页面,默认每 2 秒刷新一次,可以很直观地看到当前的 ActSess 数值。这个数值也可以通过 db2 LIST APPLICATIONS SHOW DETAIL 的结果手工统计得到,但 db2top 更省事。

需要说明的是,ACTIVE SESSION 并不等于全部连接数。一个事务(TRANSACTION)内执行完一条语句、下一条还没开始的间隙,application 的状态是 UOW Waiting,不计入 ActSess;而处于 Compiling、UOW Executing、Lock Waiting、Commit Active 以及 WLM 并发限制下的 Queued 状态的会话,都会计入。所以看到 ActSess 高,第一步不是慌,而是要知道这些会话到底卡在什么状态上。在 db2top 界面按 l(小写 L)进入 SESSIONS 页面逐条查看状态,本次看到的全部是 UOW Executing——说明应用正卡在语句执行上,而不是在等锁或者提交。

db2top 交互指令功能
d数据库概览页,查看 ActSess、HitRatio、I/O 时间等核心指标
lSESSIONS 页,逐条查看活动会话状态
D动态 SQL 缓存页,按执行时间、次数等维度排序查看
z在动态 SQL 页内按指定列排序,例如按 Avg ExecTime 排序

这个系统的 ActSess 平时基本在 10 以内,偶尔冲到 30,而这几天频繁超过 30,峰值甚至到 50。OLTP 系统的 ActSess 长期在 10 以内属于正常区间,二三十算比较繁忙,持续更高就要考虑扩容了。但一个平时只有几个活跃会话的系统突然频繁到几十个,无非两种可能:一是业务访问量激增,二是 SQL 执行时间变长。和业务系统负责人确认过,访问量没有变化,那就只剩第二种可能。

2.2 快速排除法:CPU、内存、I/O、锁等待、命中率逐个过

定位思路确定后,我按常规顺序把能一眼看到的问题先排除掉。检查结果如下:

  • CPU 利用率在正常范围,排除 CPU 瓶颈。
  • 操作系统内存使用量正常,排除内存压力。
  • db2top 里四个 I/O 指标(AvgPRdTime、AvgDRdTime、AvgPWrTime、AvgDWrTime)没有异常拉高,排除物理 I/O 瓶颈。
  • 缓冲池命中率(HitRatio)正常,排除读路径问题。
  • 没有 LOCK WAIT,排除锁竞争导致的等待。

唯一引起注意的亮点是 Deadlocks 累计值到了 218,以及 SortOvf(排序溢出)为 10。但死锁计数本身是累计值,不会直接拖慢性能;排序溢出略高,考虑到应用的 SQL 确实有较多排序操作,也不是主要嫌疑。这一轮快速排除法跑完,常规的资源瓶颈全部排除,问题指向了更隐蔽的路径。

看到这里,一个经常被忽略的点值得单独提出来:确认是数据库自身问题之后,网络故障的可能性就该从排查清单里划掉了。很多新手在内存、CPU 都正常的时候会跑去查网络,方向就偏了。

2.3 用 MON_CURRENT_SQL 抓当前正在执行的语句

排除掉外部因素后,接下来就是抓执行时间长的 SQL。我用的是 MON_CURRENT_SQL 管理视图,它可以直接看到当前正在执行的动态 SQL 和静态 SQL,并按已运行时间排序:

db2 "SELECT COORD_MEMBER MEMBER, VARCHAR(SESSION_AUTH_ID,10) USER, VARCHAR(CLIENT_APPLNAME,10) APPNAME, ELAPSED_TIME_SEC ELAPSEDTIME, VARCHAR(ACTIVITY_STATE,10) 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"

这个查询的关键在于 ORDER BY ELAPSED_TIME_SEC DESC,把已运行时间最长的语句排在最前面。注意 ELAPSED_TIME 是语句从开始到抓取时刻的运行时长,不是完整执行时间,因为抓的时候语句还没跑完。本次执行时抓到了接近 30 条正在运行的语句,也就是当时的 ACTIVE SESSION 数量接近 30,其中不少语句的已运行时间已经数秒。由于这类抓取是瞬时的,一次可能错过峰值,要多执行几次或者写个循环连续抓,才能捕捉到 ACTIVE SESSION 数量高的时候到底在跑什么。

另外,从这条 SQL 的字段里可以顺带看到 TOTAL_CPU_TIME、ROWS_READ、ROWS_RETURNED 等信息,如果某些语句 CPU 时间占比很高,说明消耗在计算上;如果 ROWS_READ 巨大而 ROWS_RETURNED 很小,多半有全表扫描或者过滤条件失效。这些信息可以给后续的优化方向提供线索。

3. 慢 SQL 的时间都花在哪了:MON_GET_PKG_CACHE_STMT 揭开等待真相

抓到了慢语句,下一步自然是分析时间花在哪。这里只靠 MON_CURRENT_SQL 不够,因为它展示的是瞬间状态,缺少完整的执行统计。我改用 MON_GET_PKG_CACHE_STMT 表函数,把语句执行过程中的等待时间、读时间、锁时间、日志写等待、排序时间全部算出来,看百分比。

3.1 用 MON_GET_PKG_CACHE_STMT 计算各项等待占比

MON_GET_PKG_CACHE_STMT 返回的是 PACKAGE CACHE 中语句的累积执行信息,包含非常细的时间分布。直接查询时字段太多,表函数返回的结果集很宽,我一般按比例聚合之后看:

db2 +w "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"

这里说明一下各个参数和字段的含义:

  • NUM_EXECUTIONS > 1 是为了排除只执行过一次的语句,避免把偶然的编译开销算进平均执行时间。
  • STMT_EXEC_TIME > 0 是排除没有执行时间的语句。
  • TOTAL_ACT_WAIT_TIME / STMT_EXEC_TIME 算出的 pct_wait 是所有活动等待占总执行时间的百分比,这是判断语句是「在等」还是「在算」的关键。
  • POOL_READ_TIME 是缓冲池读等待,LOCK_WAIT_TIME 是锁等待,LOG_DISK_WAIT_TIME 是写日志等待,TOTAL_SECTION_SORT_TIME 是段排序时间。
  • MON_GET_PKG_CACHE_STMT(NULL, NULL, NULL, -2) 中,-2 表示当前成员上的数据库级别缓存。
字段含义本次结果
pct_wait活动等待占总执行时间百分比5 条 with 语句超过 95%
pct_read缓冲池读等待占比正常
pct_lock锁等待占比正常
pct_log写日志等待占比正常
pct_sort排序等待占比正常

结果一出来,问题就清楚了:平均执行时间在 2 到 6 秒的语句里,有 5 条 with 开头的语句 PCT_WAIT 超过 95%,也就是说几乎全部时间都花在等待上,但等待的不是读、不是锁、不是日志、也不是排序。如果想进一步确认等待类型,在 SELECT 里增加 TOTAL_EXTENDED_LATCH_WAIT_TIME 字段就能看到 LATCH 等待时间——正是这些语句的等待大头。

3.2 一句话判断:等的是 LATCH 而不是锁

DB2 的 LATCH 和锁(LOCK)是两回事。锁是应用层可见的并发控制机制,有锁等待时能在监控里看到;LATCH 则是 DB2 内部为了保证多线程访问共享内存结构一致性而使用的轻量锁,完全在引擎内部,用户文档里基本找不到完整的说明。例如内存里的 PREFETCH QUEUE,用户请求的 EDU 往里添加预取任务,PREFETCHER EDU 从里面读取任务,双方并发访问时就必须用 LATCH 保护。

从监控的角度说,锁等待有专门的监控元素,而 LATCH 等待只能通过 db2pd -latches 或者 TOTAL_EXTENDED_LATCH_WAIT_TIME 这类监控元素看到。在之前的快速排除法里我已经用 db2pd -latches 检查过一次,没有异常;但这回扩大了检查窗口,用 db2pd -latches -rep 2 每隔 2 秒持续输出,终于发现了问题:每隔一段时间就会出现一波 LATCH 等待,一个 EDU ID 11665 作为 Holder 拿着一个叫 pool_table_latch 的 LATCH,旁边有 23 个 EDU 在等它。

一个 Holder 阻塞 23 个 Waiter,这意味着当时至少有 24 个会话被这一把内部 LATCH 卡住。ACTIVE SESSION 高到 50 的谜底,到这里已经浮出水面了一半。

3.3 别急着优化 SQL:先确认是不是 SQL 本身慢

看到慢 SQL 的第一反应是优化,我也这么干了。先快速建索引解决了几条有全表扫描的语句,又梳理了几条逻辑复杂的长 SQL。但做着做着突然意识到一个问题:这些逻辑复杂的长 SQL 不是最近才上线的,应用没有变更,数据库也没有变更,为什么以前不慢现在慢?

这就是一个很容易踩的坑——问题出现在 SQL 上,不代表根因就在 SQL 上。执行计划、索引、统计信息都可以造成执行时间变长,但如果没有相关的变更,就应该怀疑是环境因素发生了变化。带着这个疑问回头检查,才注意到 LIST TABLESPACES SHOW DETAIL 这款本身应该毫秒级返回的命令,居然要好几秒才能跑完。表空间相关操作变慢,这在当时并没有立刻和性能问题联系到一起,但后来证明,这正是整个事件的钥匙孔。

4. 用 STACK 逼出真相:Holder 在快照表空间,Waiter 在创建临时表

LATCH 等待是瞬时的,等几秒就消失,单纯靠 db2pd -latches 很难抓到 Holder 和 Waiter 当下正在执行什么操作。DB2 又没有文档能查到 pool_table_latch 的具体语义,这个 LATCH 名字看似眼熟,字面意思却给不了任何线索。到了这一步,常规手段已经到头了,只能用收集 STACK 的办法硬看线程在跑什么函数。

4.1 db2pd -stack all:按时间窗口持续抓取线程栈

db2pd 提供收集 DB2 运行 STACK 的能力,原本常用于分析 HANG 问题,但同样可以用来辅助 LATCH WAIT 分析,因为收集 STACK 的同时会带上每个 EDU 当时的 LATCH 持有和等待信息。由于 LATCH WAIT 不是持续存在,而是隔一段时间冒出来一次,所以需要按时间窗口连续抓取:

cd /tmp mkdir stack0531 db2pd -stack all dumpdir=/tmp/stack0531 -rep 3 10

这里的关键参数:

  • dumpdir=/tmp/stack0531 把结果写到指定目录,默认会写到 DIAGPATH,指定目录便于整理和回收。
  • -rep 3 10 表示每隔 3 秒收集一次,连续收集 10 次,一共 30 秒的窗口。为什么用 3 秒而不是更长?因为 STACK 收集对资源有明显消耗,窗口太长会拖慢数据库;窗口太短又可能漏掉瞬态的 LATCH 事件,3 秒间隔是比较折中的选择。

收集完第一轮,先确认有没有抓到 pool_table_latch:

cd /tmp/stack0531 grep pool_table_latch *stack.txt

如果 grep 结果为空,就继续下一轮收集。生产环境如果比较繁忙,收集 STACK 会明显消耗 CPU,所以这个操作必须谨慎,尽量避开业务高峰。抓到之后,结果里会区分两类:Holding Latch type 和 Waiting on latch type,分别对应持锁方和等待方。

4.2 从函数名读出操作类型:SNAPSHOT vs 创建内部临时表

拿到带 LATCH 信息的 STACK 文件后,进入对应的 .stack.txt 看调用栈。函数名虽然怪,但拼读一下还是能猜出大意。Holder EDU 的 STACK 里有这样一组连续的调用:

sqlbSnapshotTablespace -> sqlbMonSMSGetContainerPages

一看就知道,这个 EDU 正在对表空间做 SNAPSHOT,进一步是在获取 SMS 表空间容器的数据页信息。也就是说,有人在定期对表空间做监控快照,而这个快照操作本身没毛病,但因为 TEMPSPACE1 太大,取数据页的操作变得极其缓慢,长时间持有 pool_table_latch——这就是阻塞的根源之一。

Waiter EDU 的 STACK 则完全相反:

sqldTableCreateTemp -> sqldInternalCreateTable -> sqlbGetSysTempDatapool

配合 sqldEvalDataPred(evaluate predicate)和 sqlriFetch、sqlrita 这类函数,可以确认这串调用是在执行一条查询 SQL,并且需要创建内部临时表,使用系统临时表空间。查询要建内部临时表,就要往系统临时表空间的数据池里分配空间,而这一步又需要拿 pool_table_latch——恰恰被表空间快照操作捏在手里。于是 23 个等 LATCH 的 EDU 就这么被一个 SNAPSHOT 操作集体堵住了。

到这里,整个因果链已经完整闭合:数据库监控代理定时采集表空间信息 → 对 SMS 表空间做 SNAPSHOT → TEMPSPACE1 有 10G 的遗留临时文件导致快照极慢 → 长时间持有 pool_table_latch → 需要创建内部临时表的查询全部被堵 → ACTIVE SESSION 飙升、业务响应变慢。链路里的每一个环节单独看都没什么异常,串起来就是一个隐蔽的连锁故障。

4.3 analyzestack:把几十份 STACK 交给脚本自动汇总

手工翻 STACK 文件效率很低,几十份文件逐个看非常费劲。DB2 其实自带一个 Perl 脚本 analyzestack,藏在 sqllib/pd 目录下,功能是把指定目录下所有 .stack.txt 文件里重复的 STACK 归并统计,还能做 LATCH 分析。这脚本原本是 IBM DB2 支持部门的内部工具,后来放进产品里,但官方文档一直没怎么宣传,脚本开头的声明也写明「as-is」不提供官方支持。

perl ~/sqllib/pd/analyzestack -d 50 -i /tmp/stack -l
  • -d 50 指定对比 STACK 时比较的函数个数,数值越大匹配越严格。
  • -i /tmp/stack 指定 .stack.txt 所在目录。
  • -l 开启 LATCH 分析模式。

执行完会生成 LatchAnalysis.out 和 StackAnalysis.out 两个文件。如果 LATCH 分析模式检测到 Holder-Waiter 关系,会打印出对应的 EDU 和调用栈;如果没检测到,也会把 Holder 和 Waiter 信息列出来供人工核对。这个脚本的具体用法没有官方文档,只能靠 -help 自行探索,但在这个场景下,它把「手动 grep 几十个文件」变成了一条命令的事,效率提升非常明显。

5. 避坑与常见问题:TEMPSPACE1 暴涨后的五个典型坑

在正式讲解决办法之前,把这次踩到和后续复盘总结的坑集中列出来。每一条都是真实发生过的现象,照着现象对号入座,能省不少弯路。

5.1 现象:LIST TABLESPACES SHOW DETAIL 突然要好几秒

现象:一条平时毫秒级返回的表空间查看命令,执行时间拉长到数秒,而且 TEMPSPACE1 的 Total pages 显示 354661,按 32KB 页大小算正好约 10G。

原因:TEMPSPACE1 是 SMS(系统管理)表空间,其容器里堆积了大量的 TDA 临时文件。表空间信息查看操作要获取每个容器的数据页信息,文件越多越慢,进而把持有 pool_table_latch 的时间拉长。

解决:在维护窗口对数据库执行 deactivate/activate,让临时表空间重新初始化,遗留的 TDA 文件被清理,表空间大小回到正常水平,命令恢复毫秒级。

5.2 现象:TDA 文件遗留占满文件系统

现象:查看表空间容器目录(例如 T0000001/C0000003.TMP 下的文件),发现大量 SQL0xxxx.MEMBER0000.TDA 文件,每个 1MB 左右,数量成百上千,磁盘空间被占满。

原因:某时间点 ACTIVE SESSION 瞬间冲到 800 多个,大量 SQL 并发使用临时表空间,数据库扛住了,但临时表文件没有被正常清理回收,遗留成 TDA 文件。IBM Technote 里有过说明,这类 TDA 文件不被自动删除、占满文件系统是「Work as Design」。

解决:同 5.1,deactivate/activate 数据库后 TDA 文件被释放。注意这个释放动作不会立刻回收物理文件,而是让表空间回到初始状态,空间统计恢复正常。

5.3 现象:重启实例没用,必须 deactivate/activate

现象:有人先尝试重启数据库实例来清理临时表空间,结果重启后 TEMPSPACE1 依旧 10G,问题没有解决。

原因:重启实例不会重新初始化数据库内部的临时表空间元数据,遗留的 TDA 文件与表空间控制信息仍然指向原位置;deactivate 数据库再 activate 才会触发临时表空间的重建。

解决:在维护窗口执行 db2 deactivate db ,然后 db2 activate db ,确认 TEMPSPACE1 空间回落后再开放业务。生产环境操作前务必确认没有长事务和未提交的会话。

5.4 现象:ACTIVE SESSION 一度冲到 800+,是隐患的起点

现象:回看监控历史,发现某个时间点 ACTIVE SESSION 数量瞬间达到 800 多个,大量 SQL 同时使用了临时表空间,之后 TEMPSPACE1 从 0 暴涨到 10G。

原因:突发高并发导致临时表空间被大量分配,语句执行完后,SMS 表空间中的 TDA 文件没有按预期回收,遗留堆积。这类遗留问题平时没有症状,直到某天监控快照把它放大成性能瓶颈。

解决:这类隐患很难提前发现,只能靠定期查看表空间历史曲线。TEMPSPACE1 的正常值应该非常小,一旦发现持续增长或者异常暴涨,尽早安排维护窗口处理,不要等问题暴露。

5.5 现象:db2trc 可以「暂停」实例做定向验证

现象:为了验证「表空间信息查看操作会持有 pool_table_latch」这一结论,可以用 db2trc 设置断点条件,让 DB2 在进入特定函数时挂起,为人工介入争取时间。

原因:db2trc 不仅能记录函数调用,还能配合 -suspendTrace 在命中指定函数的入口时暂停 trace,等于给了一个定向观察的机会。

解决:先 db2 CONNECT TO mydb,再执行 db2trc on -crash DB2.SQLB.sqlbSMSGetContainerPages.entry -suspendTrace,然后执行 LIST TABLESPACES SHOW DETAIL 触发 SNAPSHOT 路径,观察实例是否被挂起。验证完毕用 db2trc off 关闭并清理。这个实验只在空闲环境做,生产环境切勿尝试。

6. 快速定位这类问题:一套顺手且不白忙的排查顺序

这次案例最有价值的不是某个命令,而是整套排查顺序。以后再遇到 ACTIVE SESSION 从个位数突然飙到几十的情况,我会按下面的顺序走一遍:

第一步,先用 db2top 确认 ActSess 数值和状态分布。数值高但状态是 UOW Waiting 或者 Lock Waiting,和全是 UOW Executing,处理方向完全不同。顺手把 CPU、内存、I/O 指标过一眼,排除资源瓶颈。

第二步,用 MON_CURRENT_SQL 连续抓几次当前正在执行的 SQL。注意 MON_CURRENT_SQL 是瞬时视图,抓不到峰值就多抓几次,配合 ELAPSED_TIME 排序,把候选语句池捞出来。

第三步,用 MON_GET_PKG_CACHE_STMT 算所有候选语句的时间分布。重点看 PCT_WAIT,如果等待占比超过 90%,再逐项看是 LOCK_WAIT、POOL_READ 还是 LATCH 等待。这一步能告诉你慢 SQL 到底是在「等」还是在「算」。

第四步,如果怀疑 LATCH,用 db2pd -latches -rep 2 持续观察。看到异常 LATCH 后立刻用 db2pd -stack all dumpdir=... -rep 3 10 收集线程栈,并配合 analyzestack 汇总,从函数名读出 Holder 和 Waiter 的操作类型。

第五步,回头检查表空间大小。LIST TABLESPACES SHOW DETAIL 本身就慢,往往意味着 SMS 表空间容器里有大量文件。把 TEMPSPACE1 的 Total pages 和容器目录下的 TDA 文件数量对照起来看,基本就能锁定根因。

第六步,处理与验证。维护窗口内 deactivate/activate,确认 ACTIVE SESSION 回落到个位数、LIST TABLESPACES SHOW DETAIL 恢复毫秒级,监控一到两天无复发即算闭环。

从那以后,我每次遇到 DB2 性能问题,第一反应不再是急着优化 SQL,而是先看系统临时表空间和表空间快照路径是否健康。毕竟这次翻车的教训太深刻——一个没人关心的 10G 临时表空间,能让整库的查询在毫秒与秒级之间反复横跳。希望这篇复盘对正在被同样问题卡住的你,能有点帮助。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询