EXPLAIN 让你看清 Postgres 会怎么做
EXPLAIN 打印规划器准备采用的执行计划与代价;EXPLAIN ANALYZE 则真的跑一遍语句,把估算变成测到的行数与时间。它们回答的是两个不同问题:规划器认为应该怎样做,以及数据库实际怎样做。排障时先看前者理解意图,再用后者测量现实,别把 cost 当成秒数,也别在生产库上无准备地执行一条会写数据的 ANALYZE。 计划提供需要核对的估计与观测,而不是单凭行数差异就能完成的诊断。循环次数和提前停止也会制造表面差距;估计准确时,查询仍可能受到锁等待、溢写或大量必要工作的影响。它告诉你读了哪些表、用了什么索引、预计经过多少行,以及每个节点把多少工作交给下一个节点。把计划保存进工单,和请求参数、统计信息更新时间、数据库版本一起记录,几周后的回归才有可比基线。只贴一句“慢”没有证据;保存计划能帮助定位值得检验的假设,但不能自动解释所有请求延迟。 自上而下读形状 文本计划先打印父节点,再缩进打印它的子节点。向下读可以理解节点依赖哪些输入,向上看可以理解结果怎样汇合。执行可能按需发生:上层的 LIMIT 得到足够多的行便会停止索取,下层排序却可能需要读完全部输入才能交出第一行。缩进描述依赖关系,并不是所有节点通用的执行时间顺序。 cost 使用规划器的工作量单位,不是毫秒。父节点总成本包含子节点,不能把整棵树的数字直接求和。启动成本表示返回第一行前预计投入多少工作,总成本表示完成节点的估算。实际时间和实际行数在 loops 大于一时按每次执行取平均;判断重复工作要结合循环次数,同时避免把父节点包含的子节点时间再加一次。 阅读时先问三个问题。第一,计划是否走了预期的访问路径,例如选择性很高的条件是否用了索引。第二,估算行数和实际行数是否在同一个数量级,数量级错了通常比单个节点的 cost 更值得处理。第三,数据流在哪里膨胀或等待,宽大的中间结果会把后面的排序、连接和聚合一起拖慢。沿着这三个问题读,计划就从一堆术语变成了可验证的因果链。 追踪工作量与放大点 行数最多只是线索,不能直接证明瓶颈。连续读取的大表扫描可能比随机访问索引更便宜,一个每次很快却反复执行数万次的节点也可能主导延迟。先找估算与实际第一次发生明显偏离的地方,再追踪它怎样影响后续连接、排序与内存。嵌套循环并非天然错误:外表很小、内表有匹配索引时它可能最快;危险的是外表被严重低估后仍反复访问内表。 把计划格式化为 JSON 可以让工具比较节点,手工排障先用普通 EXPLAIN 确认访问路径,再按需执行带 ANALYZE、BUFFERS 的测量。缓冲命中、缓冲读取、临时文件读写能帮助定位数据访问与排序落盘。缓冲读取不等于真实磁盘读取,因为操作系统缓存也可能提供数据;看到 read 就宣布磁盘慢,证据还不够。VERBOSE 会显示更多输出列和别名,适合定位复杂视图。 用 ANALYZE 让行数和代价对齐 EXPLAIN ANALYZE 会真实执行查询,同时给出估算与实际行数、节点耗时和循环次数。两者出入很大,可能来自统计过时、分布倾斜或列间相关性;重新收集统计有时能改善,但不能保证修好所有偏差。写入实验放到隔离数据库,并设置语句超时。事务回滚只能撤销可回滚的数据库改动,不能撤销已经分配的序列值、函数或触发器对外部系统造成的不可回滚副作用。SELECT 也可能调用带副作用的函数,不能只根据语句开头判断安全。 可以按需提高相关列的统计目标,或者建立描述同一张表中相关列的扩展统计,但这些动作也要由测量验证。扩展统计不能修复所有估计,目前也不是跨表连接选择性的通用信息来源;应先确认它改变了真正有问题的估计,再归因于统计改进。索引收益仍要根据整个查询的工作量评估。若同一语句对小租户很快、对大租户很慢,要用应用实际的预备语句路径比较通用计划和定制计划。PostgreSQL 核心并没有通用的 SQL 提示语法,不要凭空加入提示来强行选择节点。每次只改变一个变量,把结果集、计划和延迟并排保存。 ...