<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>PostgreSQL on 吾爱主机</title><link>https://blog.waihost.com/tags/postgresql/</link><description>Recent content in PostgreSQL on 吾爱主机</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sat, 01 Aug 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://blog.waihost.com/tags/postgresql/index.xml" rel="self" type="application/rss+xml"/><item><title>PostgreSQL Autovacuum：触发机制、表膨胀与参数调优</title><link>https://blog.waihost.com/posts/postgresql-autovacuum-bloat-tuning/</link><pubDate>Sat, 01 Aug 2026 00:00:00 +0800</pubDate><guid>https://blog.waihost.com/posts/postgresql-autovacuum-bloat-tuning/</guid><description>&lt;img src="https://blog.waihost.com/images/covers/postgresql-autovacuum-bloat-tuning.svg" alt="Featured image of post PostgreSQL Autovacuum：触发机制、表膨胀与参数调优" /&gt;&lt;p&gt;PostgreSQL 使用 MVCC 让读写尽量互不阻塞，但它也带来一个容易被忽略的代价：&lt;code&gt;UPDATE&lt;/code&gt; 和 &lt;code&gt;DELETE&lt;/code&gt; 不会立刻从物理文件中移除旧版本。旧元组只有在不再可能被任何事务看到后，才能由 &lt;code&gt;VACUUM&lt;/code&gt; 清理并把空间标记为可复用。&lt;/p&gt;
&lt;p&gt;Autovacuum 不是“可有可无的后台整理器”，而是 PostgreSQL 稳定运行的基础设施。它同时承担回收死元组、更新优化器统计信息、维护可见性映射以及防止事务 ID 回卷等工作。本文从触发公式、监控 SQL和调优方法三个角度，解释如何避免表膨胀与查询性能逐步恶化。&lt;/p&gt;
&lt;h2 id="一为什么会产生死元组"&gt;&lt;a href="#%e4%b8%80%e4%b8%ba%e4%bb%80%e4%b9%88%e4%bc%9a%e4%ba%a7%e7%94%9f%e6%ad%bb%e5%85%83%e7%bb%84" class="header-anchor"&gt;&lt;/a&gt;一、为什么会产生死元组
&lt;/h2&gt;&lt;p&gt;假设执行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;paid&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1001&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;PostgreSQL 通常不会在原位置覆盖旧行，而是创建新版本，并让旧版本在满足可见性条件前继续存在。这样，较早开始的事务仍可读取它应该看到的快照。&lt;/p&gt;
&lt;p&gt;当旧版本不再对任何事务可见时，它就成为 dead tuple。若清理速度长期低于写入速度，会出现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;表和索引占用空间持续增加；&lt;/li&gt;
&lt;li&gt;顺序扫描需要读取更多数据页；&lt;/li&gt;
&lt;li&gt;缓存命中效率下降，磁盘 I/O 增加；&lt;/li&gt;
&lt;li&gt;优化器统计信息过旧，可能选择错误执行计划；&lt;/li&gt;
&lt;li&gt;维护操作耗时越来越长。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;需要注意：普通 &lt;code&gt;VACUUM&lt;/code&gt; 主要把空间交还给表内部复用，通常不会立即缩小操作系统看到的文件。&lt;code&gt;VACUUM FULL&lt;/code&gt; 会重写整张表并归还空间，但需要额外磁盘空间和排他锁，因此不适合作为日常维护手段。&lt;/p&gt;
&lt;h2 id="二autovacuum-的触发公式"&gt;&lt;a href="#%e4%ba%8cautovacuum-%e7%9a%84%e8%a7%a6%e5%8f%91%e5%85%ac%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;二、Autovacuum 的触发公式
&lt;/h2&gt;&lt;p&gt;对更新和删除产生的死元组，触发阈值可近似理解为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vacuum 触发数 = autovacuum_vacuum_threshold
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; + autovacuum_vacuum_scale_factor × 表估算行数
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;PostgreSQL 18 的默认值为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;autovacuum_vacuum_threshold = 50
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;autovacuum_vacuum_scale_factor = 0.2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;一张估算为 1000 万行的表，默认需要约 200 万个更新或删除元组才触发一次 vacuum：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;50 + 0.2 × 10,000,000 = 2,000,050
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这对小表通常没问题，但对高频更新的大表可能过于宽松。分析统计信息的触发方式类似：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;analyze 触发数 = autovacuum_analyze_threshold
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; + autovacuum_analyze_scale_factor × 表估算行数
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;默认 &lt;code&gt;autovacuum_analyze_threshold&lt;/code&gt; 为 50，&lt;code&gt;autovacuum_analyze_scale_factor&lt;/code&gt; 为 0.1。&lt;/p&gt;
&lt;h2 id="三先监控再调参数"&gt;&lt;a href="#%e4%b8%89%e5%85%88%e7%9b%91%e6%8e%a7%e5%86%8d%e8%b0%83%e5%8f%82%e6%95%b0" class="header-anchor"&gt;&lt;/a&gt;三、先监控，再调参数
&lt;/h2&gt;&lt;h3 id="1-找出死元组较多的表"&gt;&lt;a href="#1-%e6%89%be%e5%87%ba%e6%ad%bb%e5%85%83%e7%bb%84%e8%be%83%e5%a4%9a%e7%9a%84%e8%a1%a8" class="header-anchor"&gt;&lt;/a&gt;1. 找出死元组较多的表
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;schemaname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n_live_tup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n_dead_tup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n_dead_tup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULLIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_live_tup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n_dead_tup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dead_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;last_autovacuum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;last_autoanalyze&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;autovacuum_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;autoanalyze_count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_stat_user_tables&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n_dead_tup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;不要只盯着 &lt;code&gt;dead_pct&lt;/code&gt;：百万行大表的 5% 可能已经很多，而几十行小表的 50% 通常影响有限。应同时观察绝对数量、增长速度、表尺寸和业务延迟。&lt;/p&gt;
&lt;h3 id="2-查看表与索引大小"&gt;&lt;a href="#2-%e6%9f%a5%e7%9c%8b%e8%a1%a8%e4%b8%8e%e7%b4%a2%e5%bc%95%e5%a4%a7%e5%b0%8f" class="header-anchor"&gt;&lt;/a&gt;2. 查看表与索引大小
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;schema_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_size_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pg_table_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;table_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_size_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pg_indexes_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;indexes_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_size_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pg_total_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total_size&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_namespace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relnamespace&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relkind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pg_catalog&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;information_schema&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_total_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;pg_stat_user_tables&lt;/code&gt; 中的元组数是统计估算，不是严格实时计数。累计统计可能有短暂延迟，而且默认在同一事务中读取时会缓存到事务结束，因此排障时应避免在长事务里反复查询后误判数据没有变化。&lt;/p&gt;
&lt;h3 id="3-检查-autovacuum-是否正在工作"&gt;&lt;a href="#3-%e6%a3%80%e6%9f%a5-autovacuum-%e6%98%af%e5%90%a6%e6%ad%a3%e5%9c%a8%e5%b7%a5%e4%bd%9c" class="header-anchor"&gt;&lt;/a&gt;3. 检查 Autovacuum 是否正在工作
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;datname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;heap_blks_total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;heap_blks_scanned&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;heap_blks_vacuumed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index_vacuum_count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_stat_progress_vacuum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;同时检查全局配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;autovacuum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;track_counts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;autovacuum_max_workers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;autovacuum_naptime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;autovacuum_vacuum_cost_delay&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;autovacuum_vacuum_cost_limit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="四针对热点大表做局部调优"&gt;&lt;a href="#%e5%9b%9b%e9%92%88%e5%af%b9%e7%83%ad%e7%82%b9%e5%a4%a7%e8%a1%a8%e5%81%9a%e5%b1%80%e9%83%a8%e8%b0%83%e4%bc%98" class="header-anchor"&gt;&lt;/a&gt;四、针对热点大表做局部调优
&lt;/h2&gt;&lt;p&gt;相比一开始就修改全局配置，更稳妥的方式是为写入最频繁的表设置存储参数：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;autovacuum_vacuum_scale_factor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;autovacuum_vacuum_threshold&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;autovacuum_analyze_scale_factor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;autovacuum_analyze_threshold&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;若 &lt;code&gt;orders&lt;/code&gt; 有 1000 万行，新的 vacuum 触发值约为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1000 + 0.02 × 10,000,000 = 201,000
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这比默认约 200 万行更及时，同时不会让所有表都采用同样激进的策略。调整后至少持续观察一个完整业务周期：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;n_dead_tup&lt;/code&gt; 是否形成可控波动而非持续上升；&lt;/li&gt;
&lt;li&gt;autovacuum 的执行次数与单次持续时间；&lt;/li&gt;
&lt;li&gt;数据盘 IOPS、吞吐和延迟；&lt;/li&gt;
&lt;li&gt;SQL 的 P95/P99 延迟；&lt;/li&gt;
&lt;li&gt;表和索引尺寸是否趋于稳定。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="五为什么调低阈值仍可能无效"&gt;&lt;a href="#%e4%ba%94%e4%b8%ba%e4%bb%80%e4%b9%88%e8%b0%83%e4%bd%8e%e9%98%88%e5%80%bc%e4%bb%8d%e5%8f%af%e8%83%bd%e6%97%a0%e6%95%88" class="header-anchor"&gt;&lt;/a&gt;五、为什么“调低阈值”仍可能无效
&lt;/h2&gt;&lt;h3 id="1-长事务阻止清理"&gt;&lt;a href="#1-%e9%95%bf%e4%ba%8b%e5%8a%a1%e9%98%bb%e6%ad%a2%e6%b8%85%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;1. 长事务阻止清理
&lt;/h3&gt;&lt;p&gt;只要某个老快照仍可能看见旧版本，VACUUM 就不能安全移除它。可先排查长时间未结束的事务：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;usename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xact_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xact_start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xact_age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wait_event_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wait_event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_stat_activity&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xact_start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xact_start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;应用连接池中的 &lt;code&gt;idle in transaction&lt;/code&gt;、未提交的批处理、长期保持的报表事务，都是常见根因。此时继续降低 scale factor，只会让 vacuum 更频繁地尝试，却未必能回收空间。&lt;/p&gt;
&lt;h3 id="2-清理能力跟不上写入速度"&gt;&lt;a href="#2-%e6%b8%85%e7%90%86%e8%83%bd%e5%8a%9b%e8%b7%9f%e4%b8%8d%e4%b8%8a%e5%86%99%e5%85%a5%e9%80%9f%e5%ba%a6" class="header-anchor"&gt;&lt;/a&gt;2. 清理能力跟不上写入速度
&lt;/h3&gt;&lt;p&gt;Autovacuum 使用基于成本的延迟机制来降低后台维护对前台业务的 I/O 冲击。如果多张大表同时高频更新，worker 数量、成本限额或磁盘能力可能成为瓶颈。&lt;/p&gt;
&lt;p&gt;调优顺序建议是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先解决长事务和异常写入；&lt;/li&gt;
&lt;li&gt;针对热点表降低触发阈值；&lt;/li&gt;
&lt;li&gt;确认磁盘有余量后，再评估 worker 数量和成本参数；&lt;/li&gt;
&lt;li&gt;每次只改一组参数，并保留前后监控数据。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;不要只为“让 vacuum 尽快结束”就无限提高成本限额，否则维护 I/O 可能直接影响在线查询。&lt;/p&gt;
&lt;h3 id="3-误把内部可复用空间当成故障"&gt;&lt;a href="#3-%e8%af%af%e6%8a%8a%e5%86%85%e9%83%a8%e5%8f%af%e5%a4%8d%e7%94%a8%e7%a9%ba%e9%97%b4%e5%bd%93%e6%88%90%e6%95%85%e9%9a%9c" class="header-anchor"&gt;&lt;/a&gt;3. 误把内部可复用空间当成故障
&lt;/h3&gt;&lt;p&gt;普通 VACUUM 后文件不缩小，并不表示它没有工作。若表仍会持续写入，保留并复用这些空闲空间往往比频繁执行 &lt;code&gt;VACUUM FULL&lt;/code&gt; 更合理。&lt;/p&gt;
&lt;p&gt;只有在一次性大规模删除、确认未来不会很快重新增长，并且能够安排锁表维护窗口时，才考虑 &lt;code&gt;VACUUM FULL&lt;/code&gt;、&lt;code&gt;CLUSTER&lt;/code&gt; 或其他表重写方案。执行前必须预留接近表及索引重建所需的额外空间。&lt;/p&gt;
&lt;h2 id="六生产调优清单"&gt;&lt;a href="#%e5%85%ad%e7%94%9f%e4%ba%a7%e8%b0%83%e4%bc%98%e6%b8%85%e5%8d%95" class="header-anchor"&gt;&lt;/a&gt;六、生产调优清单
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;保持 autovacuum 开启，不要把关闭它当成性能优化。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;pg_stat_user_tables&lt;/code&gt; 建立死元组数量、比例和清理时间的趋势图。&lt;/li&gt;
&lt;li&gt;优先为热点大表设置表级参数，避免粗暴修改全局阈值。&lt;/li&gt;
&lt;li&gt;同时监控磁盘 I/O 和业务延迟，防止维护任务挤压前台负载。&lt;/li&gt;
&lt;li&gt;排查长事务、复制槽和其他可能维持旧快照的因素。&lt;/li&gt;
&lt;li&gt;将普通 VACUUM 作为日常机制，把表重写当作有维护窗口的例外操作。&lt;/li&gt;
&lt;li&gt;定期检查事务 ID 年龄，不能只关注磁盘膨胀。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="总结"&gt;&lt;a href="#%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;总结
&lt;/h2&gt;&lt;p&gt;Autovacuum 的本质不是“定时清垃圾”，而是根据表的变更规模动态触发的一组维护任务。真正有效的优化，不是简单把参数调得更激进，而是让触发频率、清理吞吐和业务写入速度达到平衡。&lt;/p&gt;
&lt;p&gt;面对表膨胀时，应先回答三个问题：死元组为什么持续产生、为什么未被及时清理、普通 VACUUM 回收的空间是否已经被业务复用。把这些问题与长事务、I/O 能力和表级阈值结合起来分析，通常比直接执行 &lt;code&gt;VACUUM FULL&lt;/code&gt; 更安全，也更接近根因。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/routine-vacuuming.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 18 官方文档：Routine Vacuuming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/runtime-config-vacuum.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 18 官方文档：Vacuuming 配置参数&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/monitoring-stats.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 18 官方文档：累计统计系统&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/sql-vacuum.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 18 官方文档：VACUUM&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>PostgreSQL WAL、检查点与崩溃恢复：原理、参数与工程实践</title><link>https://blog.waihost.com/posts/postgresql-wal-checkpoint-crash-recovery/</link><pubDate>Fri, 31 Jul 2026 00:00:00 +0800</pubDate><guid>https://blog.waihost.com/posts/postgresql-wal-checkpoint-crash-recovery/</guid><description>&lt;img src="https://blog.waihost.com/images/covers/postgresql-wal-checkpoint-crash-recovery.svg" alt="Featured image of post PostgreSQL WAL、检查点与崩溃恢复：原理、参数与工程实践" /&gt;&lt;p&gt;生产环境里，数据库“提交成功”并不等于数据页已经刷到表文件。PostgreSQL 用 &lt;strong&gt;Write-Ahead Logging（WAL，预写日志）&lt;/strong&gt; 把变更先顺序写到日志，再异步回写堆/索引页；一旦进程或主机崩溃，就从最近检查点（checkpoint）开始 &lt;strong&gt;REDO 前滚&lt;/strong&gt;，把还没落盘的变更重放回来。理解 WAL 与检查点，才能在“提交延迟、恢复时间、磁盘占用”之间做正确取舍，而不是盲目把 &lt;code&gt;fsync&lt;/code&gt; 关掉。&lt;/p&gt;
&lt;h2 id="问题背景为什么不能每次提交都刷数据页"&gt;&lt;a href="#%e9%97%ae%e9%a2%98%e8%83%8c%e6%99%af%e4%b8%ba%e4%bb%80%e4%b9%88%e4%b8%8d%e8%83%bd%e6%af%8f%e6%ac%a1%e6%8f%90%e4%ba%a4%e9%83%bd%e5%88%b7%e6%95%b0%e6%8d%ae%e9%a1%b5" class="header-anchor"&gt;&lt;/a&gt;问题背景：为什么不能每次提交都刷数据页？
&lt;/h2&gt;&lt;p&gt;一次事务可能修改很多表页。如果每次 &lt;code&gt;COMMIT&lt;/code&gt; 都把相关数据页 &lt;code&gt;fsync&lt;/code&gt; 到磁盘：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;随机写成本高&lt;/strong&gt;：小事务会在不同数据文件之间来回寻道。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;吞吐难扩展&lt;/strong&gt;：并发提交会互相争抢刷脏页。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;恢复语义更复杂&lt;/strong&gt;：缺少统一的“已持久化边界”。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;WAL 的核心规则（官方表述）：&lt;strong&gt;对数据文件的修改，必须在描述该修改的 WAL 记录刷到永久存储之后才能写入数据页&lt;/strong&gt;。遵循这条规则后，提交时只需保证相关 WAL 已落盘，崩溃时再用日志重做（roll-forward / REDO）尚未写入数据页的变更。&lt;/p&gt;
&lt;p&gt;这带来两个工程收益：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;提交路径偏顺序写&lt;/strong&gt;：同步成本主要在 WAL 文件，而不是整页随机写。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;支持在线备份与 PITR&lt;/strong&gt;：归档 WAL + 基准备份，可回放到任意覆盖时间点。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="核心原理lsn段文件检查点与-redo"&gt;&lt;a href="#%e6%a0%b8%e5%bf%83%e5%8e%9f%e7%90%86lsn%e6%ae%b5%e6%96%87%e4%bb%b6%e6%a3%80%e6%9f%a5%e7%82%b9%e4%b8%8e-redo" class="header-anchor"&gt;&lt;/a&gt;核心原理：LSN、段文件、检查点与 REDO
&lt;/h2&gt;&lt;h3 id="1-lsn日志里的字节尺"&gt;&lt;a href="#1-lsn%e6%97%a5%e5%bf%97%e9%87%8c%e7%9a%84%e5%ad%97%e8%8a%82%e5%b0%ba" class="header-anchor"&gt;&lt;/a&gt;1. LSN：日志里的“字节尺”
&lt;/h3&gt;&lt;p&gt;每条 WAL 记录写入后，插入位置用 &lt;strong&gt;Log Sequence Number（LSN）&lt;/strong&gt; 描述，本质上是 WAL 中单调递增的字节偏移。PostgreSQL 用 &lt;code&gt;pg_lsn&lt;/code&gt; 类型表示它，可用于比较两点之间产生了多少 WAL、衡量复制与恢复进度。&lt;/p&gt;
&lt;p&gt;常用函数：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 当前 WAL 写位置
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_current_wal_lsn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 计算两个 LSN 之间的字节差（备份/复制场景常用）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_wal_lsn_diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pg_current_wal_lsn&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;0/0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="2-段文件布局pg_wal-里的-16mb-段"&gt;&lt;a href="#2-%e6%ae%b5%e6%96%87%e4%bb%b6%e5%b8%83%e5%b1%80pg_wal-%e9%87%8c%e7%9a%84-16mb-%e6%ae%b5" class="header-anchor"&gt;&lt;/a&gt;2. 段文件布局：&lt;code&gt;pg_wal&lt;/code&gt; 里的 16MB 段
&lt;/h3&gt;&lt;p&gt;WAL 默认存放在数据目录下的 &lt;code&gt;pg_wal/&lt;/code&gt;（历史版本曾叫 &lt;code&gt;pg_xlog&lt;/code&gt;）。段文件通常 &lt;strong&gt;每段 16MB&lt;/strong&gt;（&lt;code&gt;initdb --wal-segsize&lt;/code&gt; 可改），段内再按页划分（常见 8kB）。文件名是递增编号，例如从 &lt;code&gt;000000010000000000000001&lt;/code&gt; 起。&lt;/p&gt;
&lt;p&gt;运维提示：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WAL 磁盘与主数据盘分离，有助于把顺序写与随机写解耦（可在停库后迁移 &lt;code&gt;pg_wal&lt;/code&gt; 并用符号链接指回原路径）。&lt;/li&gt;
&lt;li&gt;段文件在检查点之后、确认不再需要时会被 &lt;strong&gt;回收重命名&lt;/strong&gt; 或删除；归档模式下必须先归档再回收。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-检查点恢复的安全锚点"&gt;&lt;a href="#3-%e6%a3%80%e6%9f%a5%e7%82%b9%e6%81%a2%e5%a4%8d%e7%9a%84%e5%ae%89%e5%85%a8%e9%94%9a%e7%82%b9" class="header-anchor"&gt;&lt;/a&gt;3. 检查点：恢复的“安全锚点”
&lt;/h3&gt;&lt;p&gt;检查点是 WAL 序列中的一个保证点：&lt;strong&gt;该点之前的堆/索引变更，都已反映到数据文件并刷盘&lt;/strong&gt;。检查点过程会：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把脏数据页写回磁盘；&lt;/li&gt;
&lt;li&gt;在 WAL 中写入特殊的 checkpoint 记录；&lt;/li&gt;
&lt;li&gt;把检查点位置写入 &lt;code&gt;pg_control&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;崩溃恢复时，服务器：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;读 &lt;code&gt;pg_control&lt;/code&gt; 找到最新检查点；&lt;/li&gt;
&lt;li&gt;定位 checkpoint 记录中的 &lt;strong&gt;redo 起点&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;从该 LSN &lt;strong&gt;向前扫描 WAL 做 REDO&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此：检查点越频繁，崩溃后需要重放的 WAL 通常越少，&lt;strong&gt;恢复更快&lt;/strong&gt;；但刷脏页更勤，&lt;strong&gt;运行期 I/O 更重&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;默认自动触发条件（二者先到为准）：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;参数&lt;/th&gt;
					&lt;th&gt;含义&lt;/th&gt;
					&lt;th&gt;默认（官方文档）&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;checkpoint_timeout&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;两次自动检查点的最长时间间隔&lt;/td&gt;
					&lt;td&gt;5 分钟&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;max_wal_size&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;自动检查点期间 WAL 增长的软上限&lt;/td&gt;
					&lt;td&gt;1 GB&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;checkpoint_completion_target&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;检查点 I/O 在间隔内的目标完成比例&lt;/td&gt;
					&lt;td&gt;0.9&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;min_wal_size&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;低于该占用时优先回收复用段，而不是删除&lt;/td&gt;
					&lt;td&gt;80 MB&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;CHECKPOINT&lt;/code&gt; SQL 可强制立即检查点，但文档明确：&lt;strong&gt;不适合作为常规业务操作&lt;/strong&gt;，一般留给维护窗口或排障。&lt;/p&gt;
&lt;h3 id="4-全页写full-page-writes与页撕裂"&gt;&lt;a href="#4-%e5%85%a8%e9%a1%b5%e5%86%99full-page-writes%e4%b8%8e%e9%a1%b5%e6%92%95%e8%a3%82" class="header-anchor"&gt;&lt;/a&gt;4. 全页写（Full Page Writes）与页撕裂
&lt;/h3&gt;&lt;p&gt;默认 &lt;code&gt;full_page_writes = on&lt;/code&gt;：检查点之后，某个数据页 &lt;strong&gt;第一次被修改&lt;/strong&gt; 时，会把 &lt;strong&gt;整页内容&lt;/strong&gt; 记入 WAL（FPI，full page image）。目的是防止崩溃时页只写了一半（torn page），仅靠“增量修改记录”无法正确还原。&lt;/p&gt;
&lt;p&gt;代价是：检查点刚结束后的一段时间内 WAL 量会冲高。高负载系统常通过合理调大 &lt;code&gt;max_wal_size&lt;/code&gt; / &lt;code&gt;checkpoint_timeout&lt;/code&gt;，避免检查点过于密集导致 FPI 风暴。&lt;/p&gt;
&lt;h2 id="提交耐久性synchronous_commit-与-fsync"&gt;&lt;a href="#%e6%8f%90%e4%ba%a4%e8%80%90%e4%b9%85%e6%80%a7synchronous_commit-%e4%b8%8e-fsync" class="header-anchor"&gt;&lt;/a&gt;提交耐久性：&lt;code&gt;synchronous_commit&lt;/code&gt; 与 &lt;code&gt;fsync&lt;/code&gt;
&lt;/h2&gt;&lt;p&gt;这是最容易踩坑的一组旋钮，必须分清 &lt;strong&gt;丢最近事务&lt;/strong&gt; 与 &lt;strong&gt;数据库损坏&lt;/strong&gt; 的差别。&lt;/p&gt;
&lt;h3 id="synchronous_commit可会话级调整"&gt;&lt;a href="#synchronous_commit%e5%8f%af%e4%bc%9a%e8%af%9d%e7%ba%a7%e8%b0%83%e6%95%b4" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;synchronous_commit&lt;/code&gt;（可会话级调整）
&lt;/h3&gt;&lt;p&gt;控制：在向客户端返回“成功”前，WAL 需要处理到什么程度。合法值包括：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;remote_apply&lt;/code&gt;、&lt;code&gt;on&lt;/code&gt;（默认）、&lt;code&gt;remote_write&lt;/code&gt;、&lt;code&gt;local&lt;/code&gt;、&lt;code&gt;off&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;关键语义：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;非 &lt;code&gt;off&lt;/code&gt; 的本地行为：等待 &lt;strong&gt;本地 WAL 刷盘&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;off&lt;/code&gt;（异步提交）：逻辑提交完成后即可返回，不等待 WAL 落盘；崩溃可能丢失最近若干已“成功”的事务。&lt;/li&gt;
&lt;li&gt;官方强调：异步提交的风险是 &lt;strong&gt;数据丢失，不是数据损坏&lt;/strong&gt;。恢复仍会重放到最后已刷盘的 WAL，库状态自洽，只是像最近事务被干净回滚一样。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;典型用法：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 会话级：日志/埋点等可接受极短窗口丢失
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;synchronous_commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 默认仍是 on；按事务开始时的参数值决定该事务提交模式
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;银行出钞、扣款等“客户端会据此做外部动作”的场景，不要关同步提交。&lt;/p&gt;
&lt;h3 id="fsync集群级危险"&gt;&lt;a href="#fsync%e9%9b%86%e7%be%a4%e7%ba%a7%e5%8d%b1%e9%99%a9" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;fsync&lt;/code&gt;（集群级，危险）
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;fsync = on&lt;/code&gt; 时，PostgreSQL 通过 &lt;code&gt;fsync()&lt;/code&gt; 或等价方法（见 &lt;code&gt;wal_sync_method&lt;/code&gt;）尽量把更新真正写到磁盘，保证 OS/硬件崩溃后仍可恢复到一致状态。&lt;/p&gt;
&lt;p&gt;关掉 &lt;code&gt;fsync&lt;/code&gt; 可能换来吞吐，但在掉电/系统崩溃时可能导致 &lt;strong&gt;不可恢复损坏&lt;/strong&gt;。文档只建议在可整体重建的场景（初始装载、用完即丢的批处理库、只读克隆等）临时关闭。&lt;/p&gt;
&lt;p&gt;工程对照：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;设置&lt;/th&gt;
					&lt;th&gt;主要风险&lt;/th&gt;
					&lt;th&gt;适用&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;synchronous_commit=off&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;丢最近事务，库仍一致&lt;/td&gt;
					&lt;td&gt;可丢失的短事务&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;fsync=off&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;可能损坏，需重建&lt;/td&gt;
					&lt;td&gt;几乎仅限可重建环境&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;默认两者开启&lt;/td&gt;
					&lt;td&gt;提交稍慢，耐久性强&lt;/td&gt;
					&lt;td&gt;绝大多数生产&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="可复现观测实验实验室"&gt;&lt;a href="#%e5%8f%af%e5%a4%8d%e7%8e%b0%e8%a7%82%e6%b5%8b%e5%ae%9e%e9%aa%8c%e5%ae%9e%e9%aa%8c%e5%ae%a4" class="header-anchor"&gt;&lt;/a&gt;可复现观测实验（实验室）
&lt;/h2&gt;&lt;p&gt;下面命令假设你有一个可写的测试实例。目标是把“看不见的 WAL 机制”变成可观察指标。&lt;/p&gt;
&lt;h3 id="1看当前-wal-与检查点统计"&gt;&lt;a href="#1%e7%9c%8b%e5%bd%93%e5%89%8d-wal-%e4%b8%8e%e6%a3%80%e6%9f%a5%e7%82%b9%e7%bb%9f%e8%ae%a1" class="header-anchor"&gt;&lt;/a&gt;1）看当前 WAL 与检查点统计
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wal_level&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fsync&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;synchronous_commit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkpoint_timeout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;max_wal_size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;min_wal_size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;full_page_writes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkpoint_completion_target&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_current_wal_lsn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_lsn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 新版本检查点统计在 pg_stat_checkpointer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;num_timed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;num_requested&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;num_done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;write_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sync_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buffers_written&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_stat_checkpointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- WAL 生成量与 FPI
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wal_records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wal_fpi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wal_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wal_buffers_full&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_stat_wal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;num_timed&lt;/code&gt; / &lt;code&gt;num_requested&lt;/code&gt; 会计入完成与跳过；&lt;code&gt;num_done&lt;/code&gt; 只计真正完成的检查点。&lt;/li&gt;
&lt;li&gt;空闲实例可能因“自上次检查点以来没有 WAL”而跳过定时检查点。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2制造-wal-并观察-lsn-前进"&gt;&lt;a href="#2%e5%88%b6%e9%80%a0-wal-%e5%b9%b6%e8%a7%82%e5%af%9f-lsn-%e5%89%8d%e8%bf%9b" class="header-anchor"&gt;&lt;/a&gt;2）制造 WAL 并观察 LSN 前进
&lt;/h3&gt;&lt;p&gt;在 &lt;code&gt;psql&lt;/code&gt; 中：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;EXISTS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wal_lab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bigserial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 记录写入前 LSN（psql 变量）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_current_wal_lsn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;before_lsn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;gset&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wal_lab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;x&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;generate_series&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_current_wal_lsn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;after_lsn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_wal_lsn_diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pg_current_wal_lsn&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;before_lsn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wal_bytes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;pg_wal_lsn_diff&lt;/code&gt; 返回两个 LSN 之间的字节差，便于量化一次批量写入产生了多少 WAL。若不用 &lt;code&gt;\gset&lt;/code&gt;，可先手工抄下 &lt;code&gt;before_lsn&lt;/code&gt; 再代入比较。&lt;/p&gt;
&lt;h3 id="3对比同步异步提交延迟概念实验"&gt;&lt;a href="#3%e5%af%b9%e6%af%94%e5%90%8c%e6%ad%a5%e5%bc%82%e6%ad%a5%e6%8f%90%e4%ba%a4%e5%bb%b6%e8%bf%9f%e6%a6%82%e5%bf%b5%e5%ae%9e%e9%aa%8c" class="header-anchor"&gt;&lt;/a&gt;3）对比同步/异步提交延迟（概念实验）
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;timing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LOCAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;synchronous_commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wal_lab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sync&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LOCAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;synchronous_commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wal_lab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;async&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在高并发小事务压测下，异步提交通常降低提交等待；但要清楚这是 &lt;strong&gt;用丢失窗口换延迟&lt;/strong&gt;，不是免费午餐。&lt;/p&gt;
&lt;h3 id="4强制检查点仅实验"&gt;&lt;a href="#4%e5%bc%ba%e5%88%b6%e6%a3%80%e6%9f%a5%e7%82%b9%e4%bb%85%e5%ae%9e%e9%aa%8c" class="header-anchor"&gt;&lt;/a&gt;4）强制检查点（仅实验）
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CHECKPOINT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;num_done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;write_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sync_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buffers_written&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_stat_checkpointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;观察 &lt;code&gt;write_time&lt;/code&gt; / &lt;code&gt;sync_time&lt;/code&gt; 与 &lt;code&gt;buffers_written&lt;/code&gt; 的变化，理解检查点 I/O 的代价。&lt;/p&gt;
&lt;h2 id="工程调参与排错清单"&gt;&lt;a href="#%e5%b7%a5%e7%a8%8b%e8%b0%83%e5%8f%82%e4%b8%8e%e6%8e%92%e9%94%99%e6%b8%85%e5%8d%95" class="header-anchor"&gt;&lt;/a&gt;工程调参与排错清单
&lt;/h2&gt;&lt;h3 id="恢复时间-vs-运行期-io"&gt;&lt;a href="#%e6%81%a2%e5%a4%8d%e6%97%b6%e9%97%b4-vs-%e8%bf%90%e8%a1%8c%e6%9c%9f-io" class="header-anchor"&gt;&lt;/a&gt;恢复时间 vs 运行期 I/O
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;缩短崩溃恢复&lt;/strong&gt;：减小 &lt;code&gt;checkpoint_timeout&lt;/code&gt; / &lt;code&gt;max_wal_size&lt;/code&gt; → 检查点更勤 → REDO 更少。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;减轻检查点尖峰&lt;/strong&gt;：增大二者，并把 &lt;code&gt;checkpoint_completion_target&lt;/code&gt; 维持在接近 &lt;code&gt;0.9&lt;/code&gt;，让刷脏页摊到整个间隔。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不要用检查点频率去“保证归档频率”&lt;/strong&gt;：若关心归档 RPO，应调 &lt;code&gt;archive_timeout&lt;/code&gt;，而不是把检查点拧得很紧。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="wal-目录膨胀"&gt;&lt;a href="#wal-%e7%9b%ae%e5%bd%95%e8%86%a8%e8%83%80" class="header-anchor"&gt;&lt;/a&gt;WAL 目录膨胀
&lt;/h3&gt;&lt;p&gt;常见原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;归档命令失败，段无法回收；&lt;/li&gt;
&lt;li&gt;复制槽（replication slot）落后，强制保留旧段；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wal_keep_size&lt;/code&gt; / 相关保留设置过大；&lt;/li&gt;
&lt;li&gt;批量导入、索引重建产生大量 WAL。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;排查思路：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 归档相关（若启用）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;archive_mode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;archive_command&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;archive_timeout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 当前是否在恢复（备库）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_is_in_recovery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;同时检查 &lt;code&gt;pg_wal&lt;/code&gt; 磁盘占用、复制槽延迟与归档日志报错。&lt;/p&gt;
&lt;h3 id="wal_level-选型"&gt;&lt;a href="#wal_level-%e9%80%89%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;wal_level&lt;/code&gt; 选型
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;级别&lt;/th&gt;
					&lt;th&gt;大致用途&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;minimal&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;仅崩溃恢复所需信息，体积最小；&lt;strong&gt;不足以&lt;/strong&gt; PITR/流复制&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;replica&lt;/code&gt;（默认）&lt;/td&gt;
					&lt;td&gt;支持归档与物理复制、备库只读查询&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;logical&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;在 replica 基础上增加逻辑解码所需信息&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;生产若要做流复制或连续归档，不要用 &lt;code&gt;minimal&lt;/code&gt;；文档还指出：&lt;code&gt;max_wal_senders&lt;/code&gt; 非 0 时甚至不能以 &lt;code&gt;minimal&lt;/code&gt; 启动。&lt;/p&gt;
&lt;h3 id="硬件与虚假写成功"&gt;&lt;a href="#%e7%a1%ac%e4%bb%b6%e4%b8%8e%e8%99%9a%e5%81%87%e5%86%99%e6%88%90%e5%8a%9f" class="header-anchor"&gt;&lt;/a&gt;硬件与虚假“写成功”
&lt;/h3&gt;&lt;p&gt;WAL 要求日志先于数据页持久化。若磁盘/控制器 &lt;strong&gt;谎报写完成&lt;/strong&gt;（只进缓存未真正落盘），掉电仍可能造成不可恢复损坏。务必确认 WAL 所在存储关闭了危险写缓存，或有电池备份写缓存（BBU）。&lt;/p&gt;
&lt;h2 id="常见坑对照表"&gt;&lt;a href="#%e5%b8%b8%e8%a7%81%e5%9d%91%e5%af%b9%e7%85%a7%e8%a1%a8" class="header-anchor"&gt;&lt;/a&gt;常见坑对照表
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;现象&lt;/th&gt;
					&lt;th&gt;可能原因&lt;/th&gt;
					&lt;th&gt;处理方向&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;提交延迟高、小事务 TPS 低&lt;/td&gt;
					&lt;td&gt;每次提交都在等 WAL fsync&lt;/td&gt;
					&lt;td&gt;评估 &lt;code&gt;synchronous_commit=off&lt;/code&gt;（可丢）或提升 WAL 盘延迟；关注 group commit&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;检查点期间延迟尖刺&lt;/td&gt;
					&lt;td&gt;脏页集中刷写&lt;/td&gt;
					&lt;td&gt;调大 &lt;code&gt;max_wal_size&lt;/code&gt;/&lt;code&gt;checkpoint_timeout&lt;/code&gt;，保持 &lt;code&gt;checkpoint_completion_target≈0.9&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;崩溃恢复很久&lt;/td&gt;
					&lt;td&gt;检查点过稀，REDO 区间过长&lt;/td&gt;
					&lt;td&gt;适度降低 &lt;code&gt;max_wal_size&lt;/code&gt; 或 &lt;code&gt;checkpoint_timeout&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;pg_wal&lt;/code&gt; 撑满磁盘&lt;/td&gt;
					&lt;td&gt;归档失败 / 复制槽落后&lt;/td&gt;
					&lt;td&gt;修归档、清理无用槽、扩容并监控&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;关 &lt;code&gt;fsync&lt;/code&gt; “更快”&lt;/td&gt;
					&lt;td&gt;用损坏风险换吞吐&lt;/td&gt;
					&lt;td&gt;生产禁止；可重建场景才临时关闭&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;异步提交后“丢单”&lt;/td&gt;
					&lt;td&gt;崩溃窗口内事务未刷盘&lt;/td&gt;
					&lt;td&gt;业务上接受丢失，或关键路径保持 &lt;code&gt;on&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;检查点后 WAL 暴涨&lt;/td&gt;
					&lt;td&gt;FPI（&lt;code&gt;full_page_writes&lt;/code&gt;）&lt;/td&gt;
					&lt;td&gt;避免检查点过密；观察 &lt;code&gt;pg_stat_wal.wal_fpi&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="总结"&gt;&lt;a href="#%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;总结
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;WAL 先于数据页&lt;/strong&gt;：提交持久化的关键路径是刷 WAL，而不是立刻刷所有脏表页。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;检查点定义 REDO 起点&lt;/strong&gt;：&lt;code&gt;checkpoint_timeout&lt;/code&gt; 与 &lt;code&gt;max_wal_size&lt;/code&gt; 共同决定自动检查点节奏，直接影响崩溃恢复时间与运行期 I/O。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;synchronous_commit=off&lt;/code&gt; ≠ &lt;code&gt;fsync=off&lt;/code&gt;&lt;/strong&gt;：前者可能丢最近事务但保持一致；后者可能损坏。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用 LSN 与统计视图观测&lt;/strong&gt;：&lt;code&gt;pg_current_wal_lsn&lt;/code&gt;、&lt;code&gt;pg_stat_checkpointer&lt;/code&gt;、&lt;code&gt;pg_stat_wal&lt;/code&gt; 把机制落到指标。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;调参要绑定目标&lt;/strong&gt;：RTO（恢复时间）、提交延迟、磁盘占用、复制/归档 RPO，不要只抄一组“神秘最优参数”。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;把这些机制吃透后，再去谈高可用、PITR、逻辑复制，才会知道每条配置背后买的是什么、卖的是什么。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/wal-intro.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 文档：Write-Ahead Logging (WAL)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/wal-configuration.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 文档：WAL Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/wal-async-commit.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 文档：Asynchronous Commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/wal-internals.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 文档：WAL Internals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/runtime-config-wal.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 文档：Write Ahead Log 运行参数（19.5）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/sql-checkpoint.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 文档：CHECKPOINT&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/monitoring-stats.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 文档：Monitoring Statistics（含 &lt;code&gt;pg_stat_checkpointer&lt;/code&gt; / &lt;code&gt;pg_stat_wal&lt;/code&gt;）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/functions-admin.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 文档：系统管理函数（&lt;code&gt;pg_current_wal_lsn&lt;/code&gt; 等）&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>PostgreSQL B-tree 索引：结构、扫描方式与工程实践</title><link>https://blog.waihost.com/posts/postgresql-btree-index-scan-practice/</link><pubDate>Fri, 24 Jul 2026 00:00:00 +0800</pubDate><guid>https://blog.waihost.com/posts/postgresql-btree-index-scan-practice/</guid><description>&lt;img src="https://blog.waihost.com/images/covers/postgresql-btree-index-scan-practice.svg" alt="Featured image of post PostgreSQL B-tree 索引：结构、扫描方式与工程实践" /&gt;&lt;p&gt;线上 SQL 变慢时，第一反应往往是“加个索引”。但在 PostgreSQL 里，索引不是“有就快”的开关：默认的 &lt;strong&gt;B-tree&lt;/strong&gt; 适合哪些谓词？多列索引为什么“左边优先”？为什么 &lt;code&gt;EXPLAIN&lt;/code&gt; 里会出现 &lt;code&gt;Index Scan&lt;/code&gt;、&lt;code&gt;Bitmap Heap Scan&lt;/code&gt; 或 &lt;code&gt;Index Only Scan&lt;/code&gt;？部分索引、&lt;code&gt;INCLUDE&lt;/code&gt; 覆盖列、&lt;code&gt;CREATE INDEX CONCURRENTLY&lt;/code&gt; 又分别解决什么工程问题？&lt;/p&gt;
&lt;p&gt;本文基于 PostgreSQL 官方文档（当前文档站对应 &lt;strong&gt;PostgreSQL 18&lt;/strong&gt; 手册章节），从机制讲到可复现实验与排错清单，帮助你把索引用在真正该用的地方。&lt;/p&gt;
&lt;h2 id="问题背景没有索引时数据库在做什么"&gt;&lt;a href="#%e9%97%ae%e9%a2%98%e8%83%8c%e6%99%af%e6%b2%a1%e6%9c%89%e7%b4%a2%e5%bc%95%e6%97%b6%e6%95%b0%e6%8d%ae%e5%ba%93%e5%9c%a8%e5%81%9a%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;问题背景：没有索引时，数据库在做什么
&lt;/h2&gt;&lt;p&gt;官方索引导论用了一个很直观的例子：表 &lt;code&gt;test1(id, content)&lt;/code&gt; 上频繁执行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;constant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果没有预先准备，系统只能 &lt;strong&gt;逐行扫描整表&lt;/strong&gt; 找匹配行；行数很大而命中很少时，这显然低效。若在 &lt;code&gt;id&lt;/code&gt; 上维护索引，定位匹配行可以走更高效的路径（例如在搜索树中只走几层），而不必通读整本书式的堆表。&lt;/p&gt;
&lt;p&gt;关键工程含义：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;索引要提前“预见”查询会怎样查找数据——这是 DBA/开发者的职责，而不是数据库自动替你猜对所有场景。&lt;/li&gt;
&lt;li&gt;索引创建后会随表的增删改自动维护，但也会带来 &lt;strong&gt;写放大与存储开销&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;规划器是否选用索引，依赖统计信息；因此要定期 &lt;code&gt;ANALYZE&lt;/code&gt;，并学会用 &lt;code&gt;EXPLAIN&lt;/code&gt; 验证真实计划。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="核心原理为什么日常几乎都是-b-tree"&gt;&lt;a href="#%e6%a0%b8%e5%bf%83%e5%8e%9f%e7%90%86%e4%b8%ba%e4%bb%80%e4%b9%88%e6%97%a5%e5%b8%b8%e5%87%a0%e4%b9%8e%e9%83%bd%e6%98%af-b-tree" class="header-anchor"&gt;&lt;/a&gt;核心原理：为什么日常几乎都是 B-tree
&lt;/h2&gt;&lt;p&gt;PostgreSQL 提供多种索引访问方法：&lt;code&gt;B-tree&lt;/code&gt;、&lt;code&gt;Hash&lt;/code&gt;、&lt;code&gt;GiST&lt;/code&gt;、&lt;code&gt;SP-GiST&lt;/code&gt;、&lt;code&gt;GIN&lt;/code&gt;、&lt;code&gt;BRIN&lt;/code&gt;，以及扩展如 &lt;code&gt;bloom&lt;/code&gt;。&lt;br&gt;
&lt;strong&gt;&lt;code&gt;CREATE INDEX&lt;/code&gt; 默认创建 B-tree&lt;/strong&gt;，因为它覆盖了最常见的相等与范围查询场景。其它类型需要显式 &lt;code&gt;USING method&lt;/code&gt;，例如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HASH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;column&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="b-tree-能加速哪些谓词"&gt;&lt;a href="#b-tree-%e8%83%bd%e5%8a%a0%e9%80%9f%e5%93%aa%e4%ba%9b%e8%b0%93%e8%af%8d" class="header-anchor"&gt;&lt;/a&gt;B-tree 能加速哪些谓词
&lt;/h3&gt;&lt;p&gt;官方说明：B-tree 可处理 &lt;strong&gt;可排序数据&lt;/strong&gt; 上的相等与范围查询。规划器会在索引列参与下列比较时考虑使用 B-tree：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;运算符 / 形态&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;&amp;lt;&lt;/code&gt; &lt;code&gt;&amp;lt;=&lt;/code&gt; &lt;code&gt;=&lt;/code&gt; &lt;code&gt;&amp;gt;=&lt;/code&gt; &lt;code&gt;&amp;gt;&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;基本比较&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;BETWEEN&lt;/code&gt; / &lt;code&gt;IN&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;可视为上述比较的组合&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;IS NULL&lt;/code&gt; / &lt;code&gt;IS NOT NULL&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;也可用于 B-tree&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;LIKE&lt;/code&gt; / &lt;code&gt;~&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;仅当模式为常量且 &lt;strong&gt;锚定在字符串开头&lt;/strong&gt;，如 &lt;code&gt;col LIKE 'foo%'&lt;/code&gt;、&lt;code&gt;col ~ '^foo'&lt;/code&gt;；&lt;code&gt;'%bar'&lt;/code&gt; 这类前后缀/中间匹配不行&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;补充：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;非 &lt;code&gt;C&lt;/code&gt; locale 时，模式匹配类查询可能需要特殊 &lt;strong&gt;operator class&lt;/strong&gt; 才能用索引（见手册“Operator Classes and Operator Families”相关章节）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ILIKE&lt;/code&gt; / &lt;code&gt;~*&lt;/code&gt; 更受限：模式通常需要以“不受大小写转换影响的字符”开头。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hash 索引&lt;/strong&gt; 只存 32-bit 哈希码，因此 &lt;strong&gt;只能处理简单相等比较&lt;/strong&gt;，不能替代 B-tree 的范围与排序能力。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="只有-b-tree-能带着顺序吐行"&gt;&lt;a href="#%e5%8f%aa%e6%9c%89-b-tree-%e8%83%bd%e5%b8%a6%e7%9d%80%e9%a1%ba%e5%ba%8f%e5%90%90%e8%a1%8c" class="header-anchor"&gt;&lt;/a&gt;只有 B-tree 能“带着顺序”吐行
&lt;/h3&gt;&lt;p&gt;索引除了找行，还可能直接按指定顺序交付结果，从而避免额外排序。官方明确：在当前支持的索引类型中，&lt;strong&gt;只有 B-tree 能产生有序输出&lt;/strong&gt;；其它类型返回匹配行的顺序是未指定、实现相关的。&lt;/p&gt;
&lt;p&gt;默认 B-tree 条目按 &lt;strong&gt;升序、NULLS LAST&lt;/strong&gt; 存储（相等时用表 TID 作决胜列）。因此：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;正向扫描满足 &lt;code&gt;ORDER BY x&lt;/code&gt;（即 &lt;code&gt;ASC NULLS LAST&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;反向扫描可满足 &lt;code&gt;ORDER BY x DESC&lt;/code&gt;（对应 &lt;code&gt;DESC NULLS FIRST&lt;/code&gt; 等组合规则）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;工程上特别有价值的形态是 &lt;strong&gt;&lt;code&gt;ORDER BY ... LIMIT n&lt;/code&gt;&lt;/strong&gt;：显式排序往往要处理全部候选才能取前 n 行；若有匹配的 B-tree，可直接取前 n 行，无需扫完剩余数据。&lt;/p&gt;
&lt;h3 id="唯一性约束如何落地"&gt;&lt;a href="#%e5%94%af%e4%b8%80%e6%80%a7%e7%ba%a6%e6%9d%9f%e5%a6%82%e4%bd%95%e8%90%bd%e5%9c%b0" class="header-anchor"&gt;&lt;/a&gt;唯一性约束如何落地
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;目前只有 B-tree 可声明为 unique&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;默认情况下，唯一列中的 &lt;strong&gt;多个 NULL 不视为相等&lt;/strong&gt;（允许多个 NULL）；&lt;code&gt;NULLS NOT DISTINCT&lt;/code&gt; 会把 NULL 当相等处理。&lt;/li&gt;
&lt;li&gt;定义 &lt;code&gt;PRIMARY KEY&lt;/code&gt; / &lt;code&gt;UNIQUE&lt;/code&gt; 约束时，PostgreSQL &lt;strong&gt;会自动创建&lt;/strong&gt; 对应唯一索引，无需再手写一份重复索引。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="扫描方式同一条-sql三种常见走路方式"&gt;&lt;a href="#%e6%89%ab%e6%8f%8f%e6%96%b9%e5%bc%8f%e5%90%8c%e4%b8%80%e6%9d%a1-sql%e4%b8%89%e7%a7%8d%e5%b8%b8%e8%a7%81%e8%b5%b0%e8%b7%af%e6%96%b9%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;扫描方式：同一条 SQL，三种常见“走路方式”
&lt;/h2&gt;&lt;p&gt;PostgreSQL 的索引是 &lt;strong&gt;二级索引（secondary index）&lt;/strong&gt;：索引与堆表（heap）分开存储。普通索引扫描通常既读索引，又回表读堆；匹配的索引项可能相邻，但对应堆行可能散落各处，带来随机 I/O。&lt;/p&gt;
&lt;h3 id="1index-scan点查--高选择性范围"&gt;&lt;a href="#1index-scan%e7%82%b9%e6%9f%a5--%e9%ab%98%e9%80%89%e6%8b%a9%e6%80%a7%e8%8c%83%e5%9b%b4" class="header-anchor"&gt;&lt;/a&gt;1）Index Scan：点查 / 高选择性范围
&lt;/h3&gt;&lt;p&gt;适合“索引快速锁定少量行，再回表取完整行”的路径。&lt;code&gt;EXPLAIN&lt;/code&gt; 中常见 &lt;code&gt;Index Scan using ...&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="2bitmap-index-scan--bitmap-heap-scan中等选择性或组合条件"&gt;&lt;a href="#2bitmap-index-scan--bitmap-heap-scan%e4%b8%ad%e7%ad%89%e9%80%89%e6%8b%a9%e6%80%a7%e6%88%96%e7%bb%84%e5%90%88%e6%9d%a1%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;2）Bitmap Index Scan + Bitmap Heap Scan：中等选择性或组合条件
&lt;/h3&gt;&lt;p&gt;当需要组合多个索引、或单次索引扫描无法直接表达某些 &lt;code&gt;OR&lt;/code&gt;/&lt;code&gt;AND&lt;/code&gt; 形态时，系统可：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;扫描一个或多个索引，在内存中生成 &lt;strong&gt;bitmap&lt;/strong&gt;（标记可能命中的堆位置）&lt;/li&gt;
&lt;li&gt;对 bitmap 做 &lt;code&gt;AND&lt;/code&gt; / &lt;code&gt;OR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;再按 &lt;strong&gt;物理顺序&lt;/strong&gt; 访问堆表（&lt;code&gt;Bitmap Heap Scan&lt;/code&gt;）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;官方指出：按物理序访问会 &lt;strong&gt;丢失原索引顺序&lt;/strong&gt;，因此后续若需要排序，可能再加 &lt;code&gt;Sort&lt;/code&gt;。这也解释了：bitmap 路径擅长降低随机 I/O，但不一定能顺便满足 &lt;code&gt;ORDER BY&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="3index-only-scan尽量不回表"&gt;&lt;a href="#3index-only-scan%e5%b0%bd%e9%87%8f%e4%b8%8d%e5%9b%9e%e8%a1%a8" class="header-anchor"&gt;&lt;/a&gt;3）Index Only Scan：尽量不回表
&lt;/h3&gt;&lt;p&gt;Index-only scan 的目标是 &lt;strong&gt;只靠索引回答查询&lt;/strong&gt;。官方给出两个基本前提：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;索引类型支持&lt;/strong&gt; index-only scan：B-tree &lt;strong&gt;始终支持&lt;/strong&gt;；GiST/SP-GiST 仅部分 operator class；GIN 等通常不支持（索引项往往只存原值的一部分）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查询只引用索引中存有的列&lt;/strong&gt;。例如索引在 &lt;code&gt;(x, y)&lt;/code&gt; 上：
&lt;ul&gt;
&lt;li&gt;可用：&lt;code&gt;SELECT x, y FROM tab WHERE x = 'key';&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;不可用（还要读 &lt;code&gt;z&lt;/code&gt;）：&lt;code&gt;SELECT x, z FROM tab WHERE x = 'key';&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;即便列都在索引里，PostgreSQL 仍要做 &lt;strong&gt;MVCC 可见性&lt;/strong&gt; 判断。可见性信息不在索引项里，而在堆中；因此引入 &lt;strong&gt;visibility map（可见性映射）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若堆页对应 all-visible 位已设置，可认为行对当前/未来事务可见，&lt;strong&gt;无需再访问堆元组&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;若未设置，仍需回表检查可见性——此时相对普通 Index Scan 的收益会明显下降&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此：Index Only Scan “物理上可能”，但 &lt;strong&gt;只有当表中相当比例堆页的 all-visible 位有效时&lt;/strong&gt; 才更划算。对“大量行很少变化”的表，这类扫描非常实用；写很频繁、可见性图难以保持的表，收益会打折。&lt;/p&gt;
&lt;h3 id="覆盖索引与-include"&gt;&lt;a href="#%e8%a6%86%e7%9b%96%e7%b4%a2%e5%bc%95%e4%b8%8e-include" class="header-anchor"&gt;&lt;/a&gt;覆盖索引与 &lt;code&gt;INCLUDE&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;为了让高频查询更容易走 index-only，可以做 &lt;strong&gt;covering index（覆盖索引）&lt;/strong&gt;：让索引包含查询需要返回/过滤的列。PostgreSQL 允许用 &lt;code&gt;INCLUDE&lt;/code&gt; 添加 &lt;strong&gt;非键（non-key）列&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;非键列 &lt;strong&gt;不能&lt;/strong&gt; 用于索引扫描的查找条件，也不参与唯一/排他约束判定&lt;/li&gt;
&lt;li&gt;但 index-only scan 可直接从索引叶子返回这些列，无需回表&lt;/li&gt;
&lt;li&gt;&lt;code&gt;INCLUDE&lt;/code&gt; 列会增大索引、复制数据；宽列要谨慎&lt;/li&gt;
&lt;li&gt;带 non-key 列的 B-tree &lt;strong&gt;不会使用 deduplication&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;表达式不能作为 &lt;code&gt;INCLUDE&lt;/code&gt; 列（因为无法用于 index-only scan）&lt;/li&gt;
&lt;li&gt;当前 B-tree / GiST / SP-GiST 支持该特性；叶子存 INCLUDE 值，上层导航项不包含它们&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 键列用于查找，INCLUDE 列服务于覆盖返回
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orders_user_created_cover&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;INCLUDE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="多列索引部分索引选型比多建几个更重要"&gt;&lt;a href="#%e5%a4%9a%e5%88%97%e7%b4%a2%e5%bc%95%e9%83%a8%e5%88%86%e7%b4%a2%e5%bc%95%e9%80%89%e5%9e%8b%e6%af%94%e5%a4%9a%e5%bb%ba%e5%87%a0%e4%b8%aa%e6%9b%b4%e9%87%8d%e8%a6%81" class="header-anchor"&gt;&lt;/a&gt;多列索引、部分索引：选型比“多建几个”更重要
&lt;/h2&gt;&lt;h3 id="多列-b-tree最左前缀与效率"&gt;&lt;a href="#%e5%a4%9a%e5%88%97-b-tree%e6%9c%80%e5%b7%a6%e5%89%8d%e7%bc%80%e4%b8%8e%e6%95%88%e7%8e%87" class="header-anchor"&gt;&lt;/a&gt;多列 B-tree：最左前缀与效率
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;索引最多 &lt;strong&gt;32 列&lt;/strong&gt;（含 &lt;code&gt;INCLUDE&lt;/code&gt;；编译期可改 &lt;code&gt;pg_config_manual.h&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;多键列目前由 B-tree、GiST、GIN、BRIN 等支持（与能否 &lt;code&gt;INCLUDE&lt;/code&gt; 是两件独立的事）。&lt;/li&gt;
&lt;li&gt;多列 B-tree 可用于涉及任意列子集的条件，但 &lt;strong&gt;在前导（最左）列上有约束时最高效&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;精确规则（官方表述）：对前导列的 &lt;strong&gt;相等约束&lt;/strong&gt;，加上第一个没有相等约束的列上的 &lt;strong&gt;不等式约束&lt;/strong&gt;，总是用于限制扫描的索引区间；更右侧列上的条件仍会在索引内检查（可减少回表），但不一定缩小扫描区间。&lt;/li&gt;
&lt;li&gt;新版本还有 &lt;strong&gt;skip scan&lt;/strong&gt; 优化：在合适基数下，可对缺少常规相等约束的中间列做内部动态相等约束，减少读取区间——但这是优化，不是让你随意打乱“左前缀”设计习惯。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;经验对照：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;查询形态&lt;/th&gt;
					&lt;th&gt;更合适的索引直觉&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;总是 &lt;code&gt;WHERE a = ? AND b = ?&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;(a, b)&lt;/code&gt; 复合索引&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;有时只有 &lt;code&gt;a&lt;/code&gt;，有时只有 &lt;code&gt;b&lt;/code&gt;，有时两者&lt;/td&gt;
					&lt;td&gt;可能两个单列 + 让规划器组合；或评估 skip scan / 复合&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;ORDER BY a, b LIMIT n&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;与排序方向一致的 &lt;code&gt;(a, b)&lt;/code&gt; B-tree&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;只查“活跃订单”等小子集&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;部分索引&lt;/strong&gt; 往往比全表索引更划算&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="部分索引只索引值得索引的子集"&gt;&lt;a href="#%e9%83%a8%e5%88%86%e7%b4%a2%e5%bc%95%e5%8f%aa%e7%b4%a2%e5%bc%95%e5%80%bc%e5%be%97%e7%b4%a2%e5%bc%95%e7%9a%84%e5%ad%90%e9%9b%86" class="header-anchor"&gt;&lt;/a&gt;部分索引：只索引“值得索引”的子集
&lt;/h3&gt;&lt;p&gt;部分索引建立在表的一个子集上，子集由谓词（&lt;code&gt;WHERE&lt;/code&gt;）定义，索引 &lt;strong&gt;只包含满足谓词的行&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;官方给出的核心动机：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;避免给常见值建索引&lt;/strong&gt;：若某值占比很高，查它本来也未必走索引，放进索引只会白白增大体积、拖慢写路径。&lt;/li&gt;
&lt;li&gt;缩小索引 → 命中索引的查询更快，且很多更新不必维护该索引。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;典型场景：访问日志中组织内网 IP 占比极高，但排查更关心外部 IP：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;access_log_client_ip_ix&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;access_log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;client_ip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;192.168.100.0&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;client_ip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;192.168.100.255&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;只有查询条件也落在“被索引子集”内时，规划器才能使用该部分索引；条件与谓词冲突时不会用。&lt;/p&gt;
&lt;h2 id="可复现实验从建表到读懂-explain"&gt;&lt;a href="#%e5%8f%af%e5%a4%8d%e7%8e%b0%e5%ae%9e%e9%aa%8c%e4%bb%8e%e5%bb%ba%e8%a1%a8%e5%88%b0%e8%af%bb%e6%87%82-explain" class="header-anchor"&gt;&lt;/a&gt;可复现实验：从建表到读懂 EXPLAIN
&lt;/h2&gt;&lt;p&gt;下面用一套小实验把机制落到命令（在任意 PostgreSQL ≥ 14 环境可跑；语句本身与官方语义一致）。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 1) 准备数据
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;DROP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;EXISTS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;demo_orders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;demo_orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bigserial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timestamptz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFAULT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;demo_orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ARRAY&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;paid&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pending&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cancelled&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;paid&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;paid&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;interval&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;generate_series&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;demo_orders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 2) 无业务二级索引时看计划（主键本身是 B-tree 唯一索引）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;demo_orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 3) 建立常用复合索引 + 部分索引 + 覆盖索引
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CONCURRENTLY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;demo_orders_user_created_idx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;demo_orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CONCURRENTLY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;demo_orders_pending_idx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;demo_orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pending&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CONCURRENTLY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;demo_orders_user_cover_idx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;demo_orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;INCLUDE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;demo_orders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 4) 对比计划节点名称
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;demo_orders&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;demo_orders&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pending&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;interval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;7 days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BUFFERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;demo_orders&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 若统计与可见性合适，可能出现 Index Only Scan（依赖 INCLUDE 覆盖 + visibility map）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;读计划时优先看：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;节点类型&lt;/strong&gt;：&lt;code&gt;Seq Scan&lt;/code&gt; / &lt;code&gt;Index Scan&lt;/code&gt; / &lt;code&gt;Bitmap Index Scan&lt;/code&gt; + &lt;code&gt;Bitmap Heap Scan&lt;/code&gt; / &lt;code&gt;Index Only Scan&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实际行数 vs 估算行数&lt;/strong&gt;（&lt;code&gt;rows&lt;/code&gt;）：偏差大 → 统计过期或谓词难估，先 &lt;code&gt;ANALYZE&lt;/code&gt;，再考虑扩展统计&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Buffers&lt;/strong&gt;：命中共享缓冲还是读盘；Index Only Scan 是否仍有大量 heap fetches（可见性图不给力）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;是否多了不必要的 Sort&lt;/strong&gt;：排序方向与索引 &lt;code&gt;(ASC/DESC, NULLS ...)&lt;/code&gt; 是否一致&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="生产变更create-index-concurrently"&gt;&lt;a href="#%e7%94%9f%e4%ba%a7%e5%8f%98%e6%9b%b4create-index-concurrently" class="header-anchor"&gt;&lt;/a&gt;生产变更：&lt;code&gt;CREATE INDEX CONCURRENTLY&lt;/code&gt;
&lt;/h2&gt;&lt;p&gt;普通 &lt;code&gt;CREATE INDEX&lt;/code&gt; 会对表加写锁：其它事务仍可读，但 &lt;strong&gt;INSERT/UPDATE/DELETE 会阻塞到索引建完&lt;/strong&gt;。大表上这可能长达数小时，生产不可接受。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CREATE INDEX CONCURRENTLY&lt;/code&gt; 的官方要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;构建过程 &lt;strong&gt;不获取会阻止并发写&lt;/strong&gt; 的锁（允许正常 DML 继续）&lt;/li&gt;
&lt;li&gt;代价：通常要 &lt;strong&gt;两次表扫描&lt;/strong&gt;，并等待可能修改/使用该索引的相关事务结束，&lt;strong&gt;总工作量更大、耗时更长&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;过程中索引会先以 &lt;strong&gt;INVALID&lt;/strong&gt; 状态进入系统目录；成功结束后才标为可用&lt;/li&gt;
&lt;li&gt;若扫描阶段失败（死锁、唯一冲突等），命令失败但可能留下 &lt;strong&gt;INVALID 索引&lt;/strong&gt;：查询会忽略它，却仍要为写维护它 → 推荐 &lt;code&gt;DROP&lt;/code&gt; 后重试 &lt;code&gt;CREATE INDEX CONCURRENTLY&lt;/code&gt;（或 &lt;code&gt;REINDEX INDEX CONCURRENTLY&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CREATE INDEX CONCURRENTLY&lt;/code&gt; &lt;strong&gt;不能&lt;/strong&gt; 放在事务块里；分区表并发构建有额外限制（可先对分区并发建，再非并发建分区父索引以缩短写锁定窗口）&lt;/li&gt;
&lt;li&gt;同一表上 &lt;strong&gt;同时只能有一个&lt;/strong&gt; concurrent index build&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CONCURRENTLY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sales_quantity_index&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sales_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="常见坑与排查清单"&gt;&lt;a href="#%e5%b8%b8%e8%a7%81%e5%9d%91%e4%b8%8e%e6%8e%92%e6%9f%a5%e6%b8%85%e5%8d%95" class="header-anchor"&gt;&lt;/a&gt;常见坑与排查清单
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;现象&lt;/th&gt;
					&lt;th&gt;常见原因&lt;/th&gt;
					&lt;th&gt;怎么查 / 怎么办&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;建了索引仍 &lt;code&gt;Seq Scan&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;选择性差、统计过期、函数包住列导致无法匹配、成本模型认为全表更便宜&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;EXPLAIN&lt;/code&gt;；&lt;code&gt;ANALYZE&lt;/code&gt;；避免 &lt;code&gt;WHERE func(col)=...&lt;/code&gt;（改表达式索引）；用真实参数看计划&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;LIKE '%x%'&lt;/code&gt; 不走 B-tree&lt;/td&gt;
					&lt;td&gt;B-tree 需要前缀锚定模式&lt;/td&gt;
					&lt;td&gt;换 &lt;code&gt;pg_trgm&lt;/code&gt; + GIN/GiST 等（另一条路线，不是默认 B-tree）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;多列索引“像没用”&lt;/td&gt;
					&lt;td&gt;谓词没落在最左列，或类型/排序规则不匹配&lt;/td&gt;
					&lt;td&gt;检查前导列是否有相等/范围条件；核对 &lt;code&gt;ASC/DESC NULLS&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;期望 Index Only Scan 却回表很多&lt;/td&gt;
					&lt;td&gt;查询列未覆盖；visibility map 中 all-visible 比例低&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;INCLUDE&lt;/code&gt; 覆盖；减少无意义更新；关注 vacuum 是否跟上&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;写变慢、膨胀&lt;/td&gt;
					&lt;td&gt;索引过多、宽 INCLUDE、低选择性索引&lt;/td&gt;
					&lt;td&gt;用 &lt;code&gt;pg_stat_user_indexes&lt;/code&gt; 看扫描次数；删冗余；部分索引收窄&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;CONCURRENTLY&lt;/code&gt; 后有 &lt;code&gt;INVALID&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;构建失败留下残留&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;\d table&lt;/code&gt; 确认；&lt;code&gt;DROP INDEX&lt;/code&gt; 后重建&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;唯一约束“允许多个 NULL”&lt;/td&gt;
					&lt;td&gt;默认 NULL 不相等&lt;/td&gt;
					&lt;td&gt;需要业务唯一含 NULL 时用 &lt;code&gt;NULLS NOT DISTINCT&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;辅助观测：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 索引是否被使用（需打开 stats）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;schemaname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;indexrelname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;idx_scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;idx_tup_read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;idx_tup_fetch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_stat_user_indexes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;idx_scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 无效索引排查（概念：concurrent 失败残留）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indisvalid&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indexrelid&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indisvalid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="工程实践建议可直接落地"&gt;&lt;a href="#%e5%b7%a5%e7%a8%8b%e5%ae%9e%e8%b7%b5%e5%bb%ba%e8%ae%ae%e5%8f%af%e7%9b%b4%e6%8e%a5%e8%90%bd%e5%9c%b0" class="header-anchor"&gt;&lt;/a&gt;工程实践建议（可直接落地）
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;先写清访问路径&lt;/strong&gt;：等值 / 范围 / 排序 / 覆盖返回列 / 是否只查子集。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;默认从 B-tree 开始&lt;/strong&gt;：范围、排序、唯一约束几乎都靠它；Hash 仅在“只要相等且理解其限制”时考虑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复合索引按过滤+排序设计列序&lt;/strong&gt;：高频等值列靠左，范围/排序列靠右；方向与 &lt;code&gt;ORDER BY&lt;/code&gt; 对齐。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;热点子集优先部分索引&lt;/strong&gt;：状态机里的 &lt;code&gt;pending&lt;/code&gt;、软删除后的“有效行”、租户内活跃数据等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高频只读投影再谈 &lt;code&gt;INCLUDE&lt;/code&gt;&lt;/strong&gt;：覆盖收益要压过索引变大与 dedup 失效的代价。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生产建索引用 &lt;code&gt;CONCURRENTLY&lt;/code&gt;，并处理 INVALID 残留&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用 &lt;code&gt;EXPLAIN (ANALYZE, BUFFERS)&lt;/code&gt; 闭环&lt;/strong&gt;，而不是用“感觉”判断索引是否生效。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="总结"&gt;&lt;a href="#%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;总结
&lt;/h2&gt;&lt;p&gt;PostgreSQL 索引体系的核心不是“类型清单”，而是三层决策：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;访问方法&lt;/strong&gt;：日常以 B-tree 承载相等、范围、排序与唯一性；Hash/GIN/GiST/BRIN 解决更专门的谓词与数据形态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;扫描算子&lt;/strong&gt;：Index Scan 精确定位，Bitmap 组合与减少随机 I/O，Index Only Scan 在覆盖列 + 可见性图友好时避免回表。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工程形态&lt;/strong&gt;：多列最左前缀、部分索引缩子集、&lt;code&gt;INCLUDE&lt;/code&gt; 做覆盖、&lt;code&gt;CONCURRENTLY&lt;/code&gt; 保障在线变更。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;把这三层和 &lt;code&gt;EXPLAIN&lt;/code&gt; 对上，索引才会从“玄学加速”变成可验证的性能工具。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/indexes-intro.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 18 文档：Indexes — Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/indexes-types.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 18 文档：Index Types（B-Tree / Hash / GiST / GIN / BRIN）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/indexes-multicolumn.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 18 文档：Multicolumn Indexes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/indexes-ordering.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 18 文档：Indexes and ORDER BY&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/indexes-bitmap-scans.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 18 文档：Combining Multiple Indexes（bitmap 组合）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/indexes-unique.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 18 文档：Unique Indexes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/indexes-partial.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 18 文档：Partial Indexes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/indexes-index-only-scans.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 18 文档：Index-Only Scans and Covering Indexes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/sql-createindex.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 18 文档：CREATE INDEX（含 CONCURRENTLY / INCLUDE）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.postgresql.org/docs/current/using-explain.html" target="_blank" rel="noopener"
 &gt;PostgreSQL 18 文档：Using EXPLAIN&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</description></item></channel></rss>