资讯动态

DB2性能故障诊断:临时表空间膨胀引发的LATCH竞争与STACK分析方法

发布时间:2026/10/17 22:54:40 来源:尧图企业网站定制
简介一份关于DB2数据库系统临时表空间过大引发性能问题的真实案例文档面向DBA、数据库运维工程师及DB2技术学习者尤其适合处理SQL执行变慢、活动会话数异常升高等疑难场景。资源包为单个doc文档约607KB便于直接阅读与存档。文档完整还原了某银行系统的故障排查过程常规CPU、内存、I/O、锁等待与缓冲池检查均未发现异常最终锁定临时表空间TEMPSPACE1异常增长至10GB这一根源随后通过db2top观察活动会话、db2pd分析LATCH等待、收集堆栈信息以及使用db2trc工具暂停实例等手法逐步深入给出优化SQL、调整排序参数、定期清理临时对象等解决措施。该案例工具链清晰排错逻辑严谨能帮助读者建立从现象到根因的DB2性能分析思路并在类似生产环境问题中快速借鉴。目前已有5899人学习下载是DB2运维人员值得收藏的实战参考。1. 10G 临时表空间一个没人关心的参数怎么变成性能黑洞业务人员陆续反映系统变慢监控上 ACTIVE SESSION 频繁顶到 50 以上而 CPU、内存、I/O 全部正常缓冲池命中率也漂亮甚至 LOCK WAIT 都抓不到——如果只看这些指标你大概率会转身去优化 SQL。但这次案例里真正拖垮 DB2 数据库的却是一个平时没人关心的系统临时表空间 TEMPSPACE1它膨胀到 10G而普通情况下它几乎应为 0。这 10G 的 TDA 文件像一根引信点燃了一条此前没人注意的 LATCH 竞争链监控代理周期性对表空间做 snapshotsnapshot 要扫描容器目录扫描过程握着 pool_table_latch 不放查询语句创建内部临时表时全被堵在门外。搞懂这条链路你才算真正理解 DB2 的 LATCH 机制、STACK 分析方法和 db2trc 这类底层调试工具的价值。2. 从 SQL 执行时间到等待分布用 MON_GET_PKG_CACHE_STMT 拆解慢语句2.1 活动会话数暴涨后先确认不是资源问题ACTIVE SESSION 是 DB2 数据库监控里最直观的压力指标它统计的是当前正在处理请求的会话数凡处于 Compiling、UOW Executing、Lock Waiting、Commit Active 状态的会话都算在内。通过 db2top -d 进入数据库页面后默认每 2 秒刷新一次ActSess 字段就是当前活动会话数也可以输入交互指令 l 进入 SESSIONS 页面逐条查看会话状态。一个典型的 OLTP 系统活动会话数在 10 个以内较忙时二三十个突然频繁冲到 50 以上就必须警惕。本案例中监控曲线显示之前活动会话数长期在 10 以下偶尔到 30最近几天却频繁超过 30甚至达到 50。进入 SESSIONS 页面后发现所有活动会话都处于 UOW Executing 状态说明大量会话真的在执行语句而不是在排队或等待 COMMIT。此时需要判断两种可能业务访问量激增或者 SQL 执行时间变长。既然业务侧已经确认访问量没有增加那答案基本锁定在后者。接下来就是快速排除法。CPU 利用率正常、操作系统内存使用正常db2top 中的 HitRatio 缓冲池命中率正常四个 I/O 时间指标 AvgPRdTime、AvgDRdTime、AvgPWrTime、AvgDWrTime 也都正常没有 LOCK WAIT。唯一看起来稍高的 SortOvf排序溢出为 10但考虑到应用里有较多排序语句这个量级还不至于引起系统性问题。到这里常规的体检项目几乎全过了问题只能往更深处找。2.2 MON_CURRENT_SQL抓当前正在执行的语句常规手段无效后要回到语句本身。db2top 的 D 页面可以看到 PACKAGE CACHE 里的动态 SQL再按 z 并按列号排序能按平均执行时间把慢语句排出来。案例里前几条语句的平均执行时间接近 4 秒执行次数却只有几次——这是因为应用没有使用变量绑定直接拼接 SQL 导致几乎每条语句对 DB2 来说都是新的。但平均执行时间长这个事实是确定的。更精确的工具是 MON_CURRENT_SQL 管理视图它能在任意时刻列出当前正在执行的动态和静态 SQLdb2 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这个查询按照 ELAPSED_TIME_SEC 降序排列当前正在执行的语句执行结果能真实反映此刻哪些语句正在消耗时间。执行一次就能抓到接近 30 条正在运行的语句和活动会话数互相印证。有一个细节需要注意MON_CURRENT_SQL 抓到的语句是还没执行完的ELAPSED_TIME 只是截至抓取时已经运行的时间比完整执行时间要短。如果只执行一次恰好错过高峰就要多次采样否则看到的时间分布会失真。2.3 MON_GET_PKG_CACHE_STMT把执行时间拆成等待项MON_CURRENT_SQL 解决了「现在谁在跑」的问题但回答不了「时间花在哪」。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注意 WHERE 条件里的 NUM_EXECUTIONS 1目的是过滤掉只执行过一次的语句避免把偶然的编译开销算进平均执行时间STMT_EXEC_TIME 0 则排除掉没来得及累计的语句。结果里有 5 条以 with 开头的语句平均执行时间 2 到 6 秒PCT_WAIT 全部超过 95%意味着它们的执行时间几乎全花在等待上。但等的是什么不是物理读不是锁不是写日志也不是排序——四列百分比都接近 0。这就是关键的转折点语句在等一个 MON_GET_PKG_CACHE_STMT 默认字段里没暴露的资源。继续向查询里增加 TOTAL_EXTENDED_LATCH_WAIT_TIME 字段后答案浮出水面这些语句的等待时间几乎全部来自 LATCH。3. LATCH 不是锁pool_table_latch 为何能卡住 23 个会话3.1 LATCH 机制DB2 内部共享资源的轻量级互斥DB2 文档里几乎找不到对 LATCH 的系统性介绍但理解它并不难。LATCH 是 DB2 内部的一种轻量级互斥机制目的是保护多线程并发访问内部共享数据结构时的一致性。它和应用程序层面的锁LOCK有本质区别LOCK 管理的是用户数据并发访问有锁等待检测、死锁检测、超时机制是 SQL 语义的一部分LATCH 管理的是内存里的内部结构持有时间通常极短只有几微秒到几毫秒不参与事务语义也没有死锁检测。LATCH 等待不会显式出现在 LOCK WAIT 监控里所以常规排查很容易漏掉它。举一个常见的场景DB2 内存中有一个预取队列PREFETCH QUEUE的数据结构用户请求的 EDU 需要向队列里添加预取任务PREFETCHER EDU 则不断从队列里取任务执行。两个方向的线程同时访问同一个队列必须保证互斥于是每次访问前都要先获取对应的 LATCH。如果某个 EDU 持有 LATCH 时间过长其他所有需要访问同一结构的 EDU 都会阻塞在获取 LATCH 的路上。这就是 LATCH 竞争演变成性能问题的基本路径单个持锁者操作变慢放大为大批会话排队等待。LATCH 的另一特点是命名非常晦涩。你只能看到一个类似 SQLO_LT_SQLB_PTBL__pool_table_latch 的名字没有任何文档说明它保护什么、在哪里被获取。判断一个 LATCH 的含义基本只能靠函数名和调用栈去猜。这也是为什么这次排查最终要落到 STACK 收集上——LATCH 本身只是一个症状入口真正的原因在持有者的调用路径里。3.2 db2pd -latches 捕捉 Holder 与 Waiter第一次用 db2pd -latches 检查时没有发现异常因为 LATCH 等待是间歇性的几秒钟后就消失。只有持续观察才能抓到证据。这次改用带重复间隔的写法db2pd -latches -rep 2两个关键发现第一等待不是持续存在的每隔一段时间出现一批间隔不固定第二一旦出现就是大规模排队。某次输出显示EDU ID 11665 作为 Holder 持有名为 pool_table_latch 的 LATCH另外 23 个 EDU 在等待同一个 LATCH。这意味着至少 24 个会话被钉在了这里和监控里 ACTIVE SESSION 接近 30 的数量吻合。一个 Holder 拖住 23 个 Waiter这就是性能崩坏的直接机制。pool_table_latch 从字面上看和缓冲池的表结构有关——pool 指缓冲池table 指某种表级映射但仅凭这几个词无法确定它到底保护什么。真正有用的信息在 LATCH 的等待关系里有 23 个 Waiter 同时等一个 Holder说明Holder 的临界区被异常拉长了。问题变成两个这个 LATCH 为什么会被长时间持有Waiter 们又为什么必须抢这个 LATCH3.3 从 LATCH 名字到调用路径线索在 STACK 里只靠 db2pd -latches 的输出还不够因为它只告诉你谁持有、谁等待不告诉你这些 EDU 正在执行什么操作。由于 LATCH 等待持续几秒就消失想在现场用 db2pd -agent 抓 Holder 和 Waiter 的当前状态几乎来不及唯一可靠的途径是收集 STACK。这里有一个值得反复体会的排查思路遇到 LATCH 竞争先别纠结 LATCH 本身的语义而是把问题拆成两半—— Holder 在干什么Waiter 想干什么。Holder 的 STACK 能告诉我们谁把临界区拉长了Waiter 的 STACK 能告诉我们哪些正常操作被无辜连累。本案例中Holder 和 Waiter 的 STACK 呈现出完全不同的两条调用路径一条通向表空间监控一条通向临时表创建。这个「一个操作拖累另一类操作」的格局就是后面定位根因的钥匙。4. STACK 收集与 analyzestack把黑匣子打开一条缝4.1 db2pd -stack all定时抓全库 EDU 的调用栈LATCH 等待不是稳定的复现需要持续采样才能撞上。db2pd 提供了收集 STACK 的能力而且收集时会一并带上每个 EDU 当时的 LATCH 和 LOCK 信息。命令如下cd /tmp mkdir stack0531 db2pd -stack all dumpdir/tmp/stack0531 -rep 3 10参数拆解-stack all 表示对所有 EDU 收集调用栈dumpdir 指定输出目录默认会写到 DIAGPATH 里生产环境最好显式指定到独立目录方便清理-rep 3 10 表示每隔 3 秒收集一次连续 10 次。整个采样窗口约 30 秒足够覆盖一个间歇出现的 LATCH 等待周期。采完之后先做一个快速验证在生成的 *.stack.txt 文件里搜索 pool_table_latch确认是否采样到了目标事件grep pool_table_latch /tmp/stack0531/*.stack.txt输出里出现了三类内容一部分文件里写着 Holding Latch type: (SQLO_LT_SQLB_PTBL__pool_table_latch)另一部分写着 Waiting on latch type后面都跟着相同的地址 0x7eeebae94750说明是同一个 LATCH 实例。排查到这里证据链已经闭环了一半确实抓到了 LATCH 竞争现场。生产环境注意db2pd -stack 会明显消耗系统资源采样期间数据库性能会进一步下降操作前要评估风险窗口。4.2 读 STACK 的三个线索函数名、调用链、文件行号拿到 STACK 文件后大多数 DBA 的第一反应是看不懂。函数名是 C 重整后的长字符串比如 _Z27sqlbMonSMSGetContainerPagesP12SQLB_POOL_CBiPj没有文档可查。但 STACK 真正有价值的部分不是每个函数的名字而是调用链的整体形状——它告诉你这串代码正在干什么类型的事情。Holder 的 STACK 里有这样一条链__xstat64 sqlofindn2 SQLB_FIND_BUF::next sqlbMonSMSGetContainerPages sqlbSnapshotTablespace sqlmonssag sqlmPdbRequestRouter sqleProcessSubRequestsqlbSnapshotTablespace 是表空间快照sqlbMonSMSGetContainerPages 是监控 SMS 表空间时获取容器页再往下 sqlofindn2 和 __xstat64 是文件系统层的 stat 调用。整条链合起来的意思是某个请求正在对表空间做快照监控而这个过程要逐个 stat 容器目录下的文件。这就是一个典型的监控操作被异常拉长的场景——文件越多stat 调用越多持有 LATCH 的时间越长。再来看 Waiter 的 STACKSQLO_SLATCH_CAS64::getConflictEm sqlbGetSysTempDatapool sqldInternalCreateTable sqldTableCreateTemp sqlritt2 sqldEvalDataPred sqlsfetc sqlriFetch sqlrita调用链的前半段是标准的 FETCH 路径sqlrita → sqlriFetch → sqlsfetc 表示正在读取结果集sqldEvalDataPred 是谓词求值sqlritt2 进入物化或排序路径直到 sqldTableCreateTemp → sqldInternalCreateTable → sqlbGetSysTempDatapool——创建内部临时表并且要往系统临时表空间里分配数据页。这就把 LATCH 名字里的 table 和 pool 串起来了查询执行时需要把中间结果落到临时表而获得临时表的数据页需要访问缓冲池里和表空间相关的映射结构保护这个结构的就是 pool_table_latch。两条调用链一对照结论呼之欲出监控代理周期性的表空间快照操作因为文件系统 stat 调用而长时间持有 pool_table_latch导致正常查询在创建内部临时表时全部阻塞。真正的问题不是 LATCH 本身而是 SMS 表空间下监控操作的路径设计放大了持锁时间。4.3 analyzestack官方未支持的「内部」汇总工具逐个读 STACK 文件太累DB2 提供了一个把这些文件自动汇总的工具位于 sqllib/pd 目录下名为 analyzestack 的 Perl 脚本。这个工具原本是给支持团队内部用的后来随产品发布但没有任何官方文档脚本开头的声明也明确写了 as-is、无保修无支持。用法靠 -help 查看。perl ~/sqllib/pd/analyzestack -d 50 -i /tmp/stack -l-d 50 表示对比 STACK 时比较的函数个数这个值决定了两份 STACK 被认为「相同」的相似度门槛-i 指定包含 *.stack.txt 的目录也可以直接列多个文件-l 表示额外做 LATCH 分析。脚本会生成 LatchAnalysis.out 和 StackAnalysis.out 两个结果文件前者汇总 Holder 和 Waiter 的对应关系后者按栈形状聚类输出所有 EDU 正在执行的调用路径。它的价值在于把几十份 STACK 文件压缩成两份可读的聚类报告让「哪些 EDU 在同一个函数里等待」一目了然。但对于定位本案例的根因手工读 STACK 仍然是不可替代的步骤——analyzestack 能做统计但函数调用的语义必须靠人来理解。5. 常见问题与避坑TDA 文件、deactivate/activate 和监控代理5.1 现象LIST TABLESPACES SHOW DETAIL 都要好几秒STACK 分析已经指向表空间快照于是回头检查表空间状态结果一个此前被忽略的现象浮出水面连 db2 LIST TABLESPACES SHOW DETAIL 这个平时毫秒级完成的命令现在都要好几秒才能返回。这说明表空间相关的元数据访问本身就慢和 STACK 里看到的路径完全吻合。查看 TEMPSPACE1 的具体信息时发现它已经膨胀到 10G而正常情况下系统临时表空间应该非常小甚至接近于 0。去监控系统查历史曲线找到了膨胀的时间点某个时刻 TEMPSPACE1 从 0 突然暴涨到 10G。再往前对应数据库日志那个时间点发生过一次 ACTIVE SESSION 瞬间冲到 800 多的极端情况大量 SQL 同时使用临时表空间做排序和物化留下了大量临时文件。数据库没有崩溃语句也执行成功了但临时表空间里的 TDA 文件没有按预期被清理从此埋下隐患。5.2 TDA 文件是 Work as Design不是 bug但会咬人TDA 文件是 SMS 临时表空间里存放临时数据的文件。正常情况下语句执行完、事务结束后对应的临时文件会被立即删除。但在高并发场景下如果大量语句同时在临时表空间里创建和删除文件文件系统层面的竞争会拖慢清理过程极端情况下会出现一批 TDA 文件被遗留。官方有一个 Technote 专门说明过这个问题给出的定性是 Work as Design——遗留的 TDA 文件不违反任何设计约束。真正可怕的是这个「设计如此」的后果会延迟引爆。10G 的 TDA 文件安静地躺在临时表空间里平时看不出任何异常——没有空间告警10G 对文件系统来说还不够大也没有 SQL 报错。直到某个监控代理例行访问表空间信息才把隐患变成故障SMS 表空间监控需要逐个 stat 目录下的文件文件越多stat 越慢持有 pool_table_latch 的时间越长查询创建临时表时就越容易被堵住。5.3 只重启实例无效必须 deactivate/activate找到根因后的修复动作里藏着一个大坑。很多 DBA 的第一反应是重启数据库实例让所有内部状态归零。但这个案例里重启实例无法释放那些 TDA 文件必须对数据库执行 deactivate/activate让数据库完成一次完整的停止和激活流程临时表空间才会被重新初始化。原因在于 SMS 临时表空间的容器文件是跟着数据库激活状态走的deactivate 之后容器句柄被完全释放activate 时重新扫描并创建容器目录遗留的 TDA 文件才会被清理掉。案例的实际处理结果也验证了这一点安排维护窗口执行 deactivate/activate 后临时表空间被释放LIST TABLESPACES SHOW DETAIL 恢复正常ACTIVE SESSION 回到个位数此前的所有性能症状完全消失。如果你只做了重启实例大概率会发现表空间大小纹丝不动问题原样复现。5.4 监控代理的周期性 snapshot 是触发竞争的那只手还有一个容易被忽略的教训来自监控系统本身。之所以频繁出现 LATCH 竞争而不是持续阻塞是因为触发方是监控代理的周期性表空间信息采集——每隔一段时间跑一次所以 LATCH 等待每隔一段时间出现一批间隔不固定。监控本身不慢慢的是它遍历的目录里积压了大量 TDA 文件。可以说这起事故是数据库自身的临时文件残留问题通过监控采集这个常规动作被放大了。这条因果关系对运维有直接提醒当监控系统报告性能异常时监控动作本身也可能是性能问题的参与者。出现间歇性 LATCH 等待而找不到业务语句规律时先检查是不是有定时的管理操作——比如监控采集、备份、runstats——和业务语句在抢同一批内部资源。6. 设计取舍为什么 LATCH 粒度与临时表空间类型决定了一类故障6.1 SMS 的监控路径与 pool_table_latch 的宿命梳理整个机制后会发现这起故障本质上是两个设计取舍叠加的结果。第一个取舍是 SMS系统管理表空间的文件组织方式表空间直接由文件系统管理每个容器对应用户可见目录所谓监控表空间信息就是遍历容器目录、stat 每个文件获取大小和页数。在容器文件数量很少时这个操作是轻量的但当目录里堆了成千上万个 TDA 文件一次快照的代价就变得不可忽视。第二个取舍是 pool_table_latch 的粒度它所保护的映射结构在 LATCH 层级里属于较粗的粒度所以任何涉及临时表空间页分配、表空间元数据访问的操作都要经过它。设计者做这个选择时假设的是临界区操作足够短——毕竟正常情况下一个容器目录里文件数很少stat 很快。两个假设同时失效时故障就发生了文件系统层累积了异常多的文件监控操作被拉长粗粒度的 LATCH 就把等待放大到了整个数据库。6.2 db2trc 暂停实例验证 sqlbSMSGetContainerPages 的调用确认推断可以用 db2trc 做一个小实验。db2trc 是 DB2 提供的底层跟踪工具可以截获运行过程中调用的函数它有一个特殊的用法——在某个函数入口把实例挂起让现场停下来db2 CONNECT TO mydb db2trc on -crash DB2.SQLB.sqlbSMSGetContainerPages.entry -suspendTrace设置的含义是当任意线程进入 sqlbSMSGetContainerPages 这个函数时触发挂起条件暂停后续 trace把现场冻结住。然后另开一个终端执行 db2 LIST TABLESPACES SHOW DETAIL会发现命令卡住——因为实例已经被挂起在 sqlbSMSGetContainerPages 的入口处此时用 db2pd -stack 查看能看到聚集在该函数处的调用栈。这个实验用可控的方式验证了线上观察到的路径表空间信息查看操作确实会调用 sqlbSMSGetContainerPages并且这个函数是监控路径上最容易被拉长的一环。线上不敢做的操作在测试环境用 db2trc 挂起复现是确认推断的最稳妥方式。6.3 我从此养成的习惯从那以后每遇到一个数据库性能问题我都会在动手优化 SQL 之前先问一句有没有什么后台操作在周期性地触碰同一个共享结构。案例里最值得反思的不是 LATCH 机制本身而是「常规体检全部正常」这个结果给人造成的安全感——CPU 正常、内存正常、I/O 正常于是被引导去一条条梳理 SQL差点把方向走歪。实际上第一轮 db2pd -latches 没抓到异常也应该引起更多警惕间歇性出现的症状背后往往藏着周期性的触发源。现在我排查性能问题时会把 LIST TABLESPACES SHOW DETAIL 的响应时间也列为常规检查项一个元数据命令从毫秒级变成秒级本身就是内部结构异常的强烈信号看到 TDA 文件堆积也不再想着只是清理文件而是先确认它的来源再决定是调整临时表空间配置还是让监控代理避开高峰。这条从现象到 STACK 再到 LATCH 的分析链路比任何单个工具都更能帮你找到藏在角落里的根因。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑