跳转到主要内容

43 术语表

本书常用术语,每条用一到三句话解释,并附上讲解该概念的小节链接。

Postgres 的大多数术语都有具体实物所指:页上的一个槽位、元组头里的一个计数器,或表旁边的一个文件。这里的每个词条都链接到用真实输出展示该事物的小节,你可以前去亲眼看看它如何运作。

词头采用统一约定:SQL 命令和关键字用大写(VACUUM、NOT VALID),配置项、函数及其他标识符保留实际输入时的写法(work_mem、ctid),其余英文词头仅首词首字母大写。词条按英文字母逐字排序,忽略大小写和标点。

A

ACID

事务给出的四项承诺:原子性、一致性、隔离性和持久性。在 Postgres 中,每一项都对应一种可以观察到的具体机制,从提交记录到 fsync。参见四项承诺,四种机制。

咨询锁(Advisory lock)

加在整数键上的锁,键的含义由应用自行定义,用于对行以外的事物实现互斥(例如“每次只运行一个夜间汇总任务”)。优先使用事务级咨询锁,提交时会自动释放。参见按事务取得咨询锁,而不是按会话取得。

ANALYZE

对表进行采样并保存统计信息的命令,规划器据此估算。足够多的行发生变化后,自动清理会运行该命令。参见ANALYZE 读取样本,而不是整张表。

异常(Anomaly)

一种无法由事务按任何先后顺序逐个执行而产生的结果,例如丢失更新或写偏差。隔离级别以其允许哪些异常来定义。参见只有可串行化能排除所有异常。

自动清理(Autovacuum)

当表的死元组数、插入行数或事务 ID 年龄超过阈值时,对表执行清理和分析的后台工作进程。阈值随表大小增加,因此默认值对大表来说过于迟钝。即使关闭自动清理,它在事务年龄达到 2 亿时强制触发的防回卷清理仍会运行。参见自动清理会因死行、插入或年龄而触发。

B

后端进程(Backend)

Postgres 为每个连接启动的操作系统进程。它解析、规划并执行该会话的查询,保存其状态,不为其他会话服务。参见每个连接都是一个进程。

基础备份(Base backup)

通过 pg_basebackup 或备份工具,在服务器运行时复制整个数据目录得到的物理副本。还原这一副本,再从备份开始的位置重放归档 WAL,就是物理恢复和时间点恢复的工作方式。参见数据量大时,只有物理备份恢复得快。

位图扫描(Bitmap scan)

先从索引收集所有匹配行的地址,再按物理顺序访问堆页、每页只访问一次的扫描方式。超过 work_mem 后,位图会变为有损形式,只记录页,不再记录行。参见索引能帮多少忙,取决于匹配行有多集中。

膨胀(Bloat)

表或索引中既不存放存活数据、近期也不会复用的空间,由两次清理之间积累的死元组留下。如果表占用的空间是存活数据大小的五倍,每次扫描就得读取五倍的页。参见清理释放表内空间,而不是磁盘空间。

BM25

一种相关度评分,将词在文档中的出现频率与其在整个语料库中的稀有程度结合起来衡量。内置排名不具备这一能力;较新的搜索扩展会在索引中保存所需统计信息。参见搜索扩展把 BM25 放进索引。

BRIN

块范围索引(Block Range INdex):为每一段页记录最小值和最大值,就像搬家纸箱上的标签。体积很小;当物理顺序与该列的顺序一致时,效果几乎与 B-树一样好,不一致时则毫无用处。参见BRIN 为每个页范围保留一份汇总。

B-树(B-tree)

默认索引:由 8 KB 页组成的浅层有序树,叶子按顺序保存每个键及其行地址。读取三四个页,就能从几百万行中找到一行。参见B-树是一棵由页组成的浅树。

缓冲区(Buffer)

共享内存中的一个 8 KB 槽位,容纳一个页。每个进程都通过缓冲区读写页,从不直接从文件中读写。参见shared_buffers 是一个页数组。

环形缓冲区(Buffer ring)

大型扫描、清理和批量写入会循环使用的一小组专用缓冲区,让这些操作淘汰自己的页,而不是你的页。在 PostgreSQL 18 中,大型顺序扫描的环形缓冲区大小为 2,304 KB。参见大型操作会使用环。

Buffers(执行计划中的字段)

explain (analyze) 中统计节点访问页数的一行:hit 表示在共享缓冲区中命中,read 表示从下一层读取,此外还有 dirtied 和 written。与耗时不同,这些计数不会随负载变化,因此是本书的主要证据。参见缓冲区才是证据。

C

检查点(Checkpoint)

定期将所有脏缓冲区写入数据文件并执行 fsync,让崩溃恢复可以从这里开始,而不必从最初的历史开始。参见检查点为恢复设定上限。

检查点进程(Checkpointer)

执行检查点的后台进程,将脏缓冲区的写入分散到检查点间隔的大部分时间里(由 checkpoint_completion_target 控制,默认值为 0.9)。与它配合的后台写进程会赶在时钟扫描之前写出脏缓冲区,因此后端进程很少需要自己写出脏缓冲区。参见你的查询不该负责写出脏页。

Citus

一个开源扩展(AGPL-3.0),将一组 Postgres 服务器组成一个分布式数据库:你连接协调节点,工作节点保存分片,而路由、合并和两阶段提交都在 Postgres 内部处理。参见Citus 在 PostgreSQL 内部路由与合并。

时钟扫描(Clock sweep)

Postgres 选择要淘汰的缓冲区所用的算法:指针循环扫过缓冲区,跳过被钉住的缓冲区,将遇到的使用计数减一,并淘汰遇到的第一个使用计数为零的缓冲区。冷数据的一次扫描无法挤掉热点页。参见时钟扫描挑选受害者。

排序规则(Collation)

比较和排序文本的规则:'a' 是否排在 'B' 前面,两种拼写是否相等。每个文本索引都依据某种排序规则建立;如果操作系统升级改变了规则,索引就可能损坏。参见排序规则决定什么算相等。

共置(Colocation)

一种分片布局,按相同的键拆分相关表,让同一租户在所有这些表中的行落在同一分片,并就地连接。所有租户都会读取的小表则作为引用表复制到每个节点。参见分片键就是整个设计的核心。

列式存储(Column store)

将每一列保存在各自的压缩文件中、只读取查询指定列的数据库。它擅长扫描数十亿行,在点查和更新上则处于劣势。参见行式存储为了用两列,必须读取所有列。

协调遗漏(Coordinated omission)

一种基准测试错误:负载生成器等到缓慢的响应返回后才继续发送请求,因此从未测量那些被停顿延迟的请求。闭环测试天生就有这一问题。参见闭环会隐藏停顿。

COPY

批量加载通道:一条语句以流的方式传送许多行,每行开销远低于逐行插入。其 freeze 选项能将已冻结的行写入在同一事务中创建或截断的表。参见COPY 是批量加载路径。

相关性(Correlation)

取值范围为 -1 到 1 的统计量,衡量列值顺序与行的物理顺序有多接近。规划器使用它的平方来估算索引扫描的代价。参见相关性决定索引扫描的代价。

代价(Cost)

规划器对执行计划的估价,使用以顺序读取一个页为基准的自定义单位(seq_page_cost = 1.0)。它根据估计行数推算而来,并非耗时。参见代价是用虚构单位做出的猜测。

覆盖索引(Covering index)

包含查询所需全部列的索引,常借助 include 实现,从而可以用仅索引扫描完成查询。应覆盖热点查询,而不是所有查询。参见覆盖索引:覆盖热点查询,而不是每个查询。

CREATE INDEX CONCURRENTLY

一种不阻塞写入的索引构建方式,代价是两次全表扫描,并等待较早的事务结束。构建失败会留下一个 INVALID 索引,查询会忽略它;删除后重试即可。参见选择与表流量匹配的索引构建方式。

公用表表达式(CTE)

公用表表达式,即 with 子句。自 PostgreSQL 12 起,只引用一次的 CTE 会被内联;materialized 会强制它单独执行,这可能让外层查询无法利用你的索引。参见只使用一次的 CTE 会被内联,而不是设栅栏。

ctid

行版本的物理地址,由页号和行指针号组成,例如 (0,3)。每张表都有这个隐藏列,每个索引都指向它。参见行指针让元组移动而不改变地址。

D

数据校验和(Data checksums)

每个数据页上的校验和,让磁盘上的位翻转触发错误,而不是悄悄返回错误的行。自 PostgreSQL 18 起,initdb 默认启用。参见启用校验和后,连读取也会写 WAL。

死锁(Deadlock)

两个事务各自等待对方所持有锁的僵局。经过 deadlock_timeout 指定的时间后,Postgres 会检测到死锁并中止其中一个事务;解决办法是按一致的顺序获取锁。参见死锁是顺序错误。

死元组(Dead tuple)

任何事务都不可能再看到的行版本:它已被删除或替换,而且早于所有仍在使用的快照。页内剪枝和清理会回收它的空间;在此之前,它仍占用页。参见只有没有快照能看到的版本才是死版本。

去重(Deduplication)

B-树在 PostgreSQL 13+ 中采用的存储方式:重复键只保存一份,后面跟上拥有该键的各行的倒排列表。它能让外键和状态列上的索引缩小到原来的约三分之一。参见去重:重复的键只存一次(PostgreSQL 13+)。

脏缓冲区(Dirty buffer)

在内存中已被修改、尚未写入文件的页。修改已经安全地保存在 WAL 中;检查点进程、后台写进程或需要该缓冲区的后端进程稍后会把它写出。参见你的查询不该负责写出脏页。

计算与存储分离(Disaggregated storage)

一种托管数据库设计(Aurora、AlloyDB、Neon):让 Postgres 运行在缓冲区缓存之上,并用根据传来的 WAL 构建页的存储服务替代磁盘。在这些系统中,检查点调优和副本行为都会发生变化。参见分离式存储改变了部分规则。

E

排他约束(Exclusion constraint)

唯一约束的推广:不允许任意两行同时满足一组操作符条件,例如“同一房间的任意两条预订时间都不得重叠”。它通过 GiST 索引强制执行。参见排他约束让重叠变得不可能。

扩展与收缩(Expand and contract)

分步发布不兼容模式变更的方法,让新旧代码都能正常运行:先添加新结构,部署使用它的代码,回填数据,再在后续部署中删除旧结构。参见重命名要用扩展与收缩发布。

EXPLAIN

显示 Postgres 所选执行计划的命令。单独的 explain 只做估算;explain analyze 会执行查询,并将实际结果与预测并列显示,因此对写操作应使用事务包裹,并在之后回滚。参见EXPLAIN 负责猜测;EXPLAIN ANALYZE 负责运行。

表达式索引(Expression index)

基于表达式结果建立的索引,例如 lower(email),为使用完全相同表达式的查询服务。ANALYZE 还会收集其统计信息,以修正估算。参见索引只存储你交给它的值。

扩展统计信息(Extended statistics)

通过 create statistics 创建的跨列统计信息,用于规划器“各列相互独立”的假设不成立时。ndistinct 修正分组数估算,mcv 修正相关列过滤条件的估算。参见扩展统计信息联合测量多列。

扩展(Extension)

由类型、函数和索引方法组成的包,通过 create extension 安装,按版本跟踪管理。托管服务只允许安装其允许列表中的扩展。参见扩展是数据库跟踪的一个软件包。

F

故障切换(Failover)

主库故障时,将副本提升为主库。难点在于确认原主库确实已故障,并将其隔离;任何一步没做好,都可能让两台服务器同时接受写入(脑裂),而没有机制能合并两边的数据。应使用专门的工具,不要自行写脚本。参见一次调用提升副本,开启新时间线。

填充因子(Fillfactor)

一个存储参数,让插入操作在页填满前就停下来,留出空间;对表而言,默认值为 100%。预留空间供更新使用:新版本如果能放在同一页中,就可能实现 HOT 更新。参见填充因子为更新留下空间。

外键(Foreign key)

要求某个值必须存在于另一张表的键中的约束。插入子表记录时,检查父表的代价很低;删除父表记录时,则会扫描子表,除非你自行在引用列上建立索引。参见外键要从两个方向检查,代价各不相同。

分支文件(Fork)

构成一张表的文件之一:main 保存行,_fsm 保存空闲空间映射,_vm 保存可见性映射,不记日志表还会有一个空的 _init。参见表不只有主文件。

空闲空间映射(Free space map)

一个小型分支文件,粗略记录每个页还有多少空间,让插入和更新可以填充已有页,而不是扩展文件。清理会保持其信息更新。参见插入无需扫描表也能找到空间。

冻结(Freezing)

清理过程中的一个步骤:当插入事务足够老时,将元组标记为永久对所有事务可见,使其能跨越事务 ID 回卷。全冻结页以后无需再为冻结而访问。参见旧行必须在 XID 计数器回卷前被冻结。

fsync

强制将已写入的数据落到持久化存储上的系统调用。每次提交都要等待对 WAL 的一次 fsync,因此磁盘的 fsync 延迟决定了提交速率。参见每次提交都要等待磁盘。

整页写入(Full-page write)

每次检查点之后,页首次修改时记入日志的 8 KB 页镜像,让恢复过程能修复撕裂页(断电时只写了一半的页)。这就是检查点之后 WAL 量会骤增的原因。参见检查点后的第一次变化会记录整页。

G

通用计划(Generic plan)

在不知道参数值的情况下,为预备语句生成的执行计划。从第六次执行起,如果它看起来不比定制计划更昂贵,Postgres 就会一直改用它,这可能让原本很快的查询变慢。参见预备语句运行五次后切换计划。

遗传查询优化器(GEQO)

遗传查询优化器:从连接 12 张表开始(geqo_threshold),以随机搜索替代穷举来寻找连接顺序。规划仍然很快;计划追求足够好,而不保证最优。参见连接顺序是一场搜索,而且搜索会变得昂贵。

GIN

通用倒排索引(Generalized Inverted iNdex):每个不同的元素(jsonb 的键、数组标签或词)对应一个条目,记录包含该元素的行,就像书末的索引。参见GIN 搜索很快,更新很贵。

GiST

通用搜索树(Generalized Search Tree),其条目保存一个边界值,覆盖下层所有内容。它索引的是重叠关系和距离,可用于范围、几何数据、三字符组相似度以及排他约束。参见GiST 为没有顺序的数据建立索引。

组提交(Group commit)

一次 WAL 刷新涵盖多个几乎同时提交的事务。Postgres 会自动完成这一点,这也是吞吐量可以超过“每次 fsync 提交一个事务”的原因。参见许多提交可以共享一次刷新。

H

哈希索引(Hash index)

保存 32 位哈希码的索引,只支持一个操作符 =,大小不取决于键的宽度。对令牌之类的长随机值做等值查询时值得使用;其他情况都用 B-树。参见哈希索引只有在长键等值查询上才占优。

哈希连接(Hash join)

先将内侧输入加载到哈希表,再以流式方式逐条处理外侧输入的连接方式。它是大型无序输入的主力;超过 work_mem 乘以 hash_mem_multiplier 的内存限额后,会拆成多个批次落盘处理。参见每种连接方法只适合某些行数。

堆(Heap)

Postgres 的表:一堆无序的页,哪里有空间,行就放在哪里。每个索引,包括主键索引,都通过 ctid 指向堆中的行。参见行指针让元组移动而不改变地址。

提示位(Hint bits)

元组上的标志位,缓存相关事务是否已提交,由第一个检查提交日志(pg_xact)的读取者设置。设置提示位会弄脏页,因此 select 也可能产生写入。参见读取也可能写入。

直方图(Histogram)

即 histogram_bounds 统计信息:将列中不属于高频值列表的值分成约 100 个行数相等的桶。规划器据此估算范围过滤条件。参见pg_stats 是规划器看到的全部信息。

HNSW

分层可导航小世界(Hierarchical Navigable Small World),一种近似向量索引:它是分层图,每个向量都与近邻相连,少数向量还位于更稀疏的上层,如同快车停靠站;搜索时以贪心方式沿图行进。参见HNSW 会贪心地遍历图,也可能漏掉结果。

HOT 链(HOT chain)

同一行的多个版本,由 HOT 更新通过元组头链接起来,全部位于同一页。索引指向链的第一个槽位;页内剪枝随后会将链头变为重定向行指针。参见HOT 查找会沿着版本链走,不离开所在页。

HOT 更新(HOT update)

堆内元组(heap-only tuple)更新:不改动索引列,且新版本能放在同一页中,因此无需通知任何索引。非 HOT 更新(冷更新)则会向每个索引写入一个条目。参见HOT 需要一次未被索引覆盖的变化,以及页上的空间。

I

标识列(Identity column)

由序列填充的整数键,写作 generated always as identity,用来替代 serial。序列从不回滚,因此出现空号很正常。参见让数据库为行编号,永远不要相信顺序。

事务中空闲(Idle in transaction)

开启事务后便不再发出请求的会话,常见于应用等待网络调用时。它仍持有锁和快照,因此清理无法回收被它拖住的旧版本。参见idle_in_transaction_session_timeout 最重要。

不安全的直接对象引用(IDOR)

不安全的直接对象引用(insecure direct object reference):接口收到什么 ID 就加载什么对象,却不检查调用者是否有权查看。连续递增的 ID 会让这种漏洞轻易遭到利用。参见计数器是在邀请攻击。

仅索引扫描(Index-only scan)

只从索引获取查询所需内容的扫描方式;对可见性映射标记为全可见的每个页,都会跳过堆访问。计划中的 Heap Fetches 统计仍然不得不回堆检查的行数。参见最好的堆页,是你永远不会读到的。

索引扫描(Index scan)

读取索引并随即从堆中取出每个匹配行的扫描方式,因此行按索引顺序输出。只取少量行时代价很低;匹配行散布在很多页上时则很昂贵。参见索引能帮多少忙,取决于匹配行有多集中。

IOPS

每秒 I/O 操作数,与延迟和吞吐量一起作为存储服务的指标出售。预置 IOPS 只是上限:实际能获得的是这个上限与“在途 I/O 数除以延迟”两者中的较小值。参见IOPS 受在途读取限制,而不是由你付费购买的数量决定。

隔离级别(Isolation level)

决定事务能看到其他事务多少工作的设置。读已提交(默认级别)每条语句获取一次快照,可重复读每个事务获取一次快照,可串行化还保证结果等价于某种逐个执行事务的顺序。参见有目的地选择隔离级别。

IVFFlat

一种近似向量索引,将向量聚成 lists 个簇,只搜索离查询向量最近的 probes 个簇。默认只探测一个簇是个陷阱:在我们的测试中,recall@10 为 0.636,探测五个簇时则为 1.000。参见IVFFlat 的默认一次探测是陷阱。

J

即时编译(JIT)

将查询表达式即时编译为机器码的机制,在估算代价超过 jit_above_cost 时触发。这是基于估算的一次押注:长时间的分析扫描可以获益,短查询则会吃亏。参见JIT 是规划器根据自身估算下的一次赌注。

K

键集分页(Keyset pagination)

记住上一页结束位置的分页方式(where (created_at, id) < ($1, $2))。每一页的代价都一样;offset 则会读取它跳过的每一行。参见OFFSET 会读取它跳过的每一行。

L

词素(Lexeme)

全文搜索所存储的规范化单词:转为小写、提取词干、去除停用词,并保留位置。“The deploys failed” 会变成 'deploy' 和 'fail'。参见文本会变成词素。

行指针(Line pointer)

位于页头部的 4 字节槽位,指向页内更靠后的一个元组。索引指向行指针,因此元组可以在页内移动而不改变地址。参见行指针让元组移动而不改变地址。

锁模式(Lock modes)

八种表锁级别,从 ACCESS SHARE(每条 select 都会获取)到 ACCESS EXCLUSIVE(大多数 alter table 形式都会获取,连读取也会阻塞)。每条语句都会获取某种锁,因此要问清楚是什么锁、持有多久。参见八种模式、冲突矩阵,以及每种 DDL 取得了什么锁。

锁队列(Lock queue)

等待锁的会话队列。如果某个请求与已在等待的请求冲突,就会排到后者后面。因此,一条等待长查询结束的 alter table 会阻塞排在它后面的所有查询。参见一个等待中的锁会阻塞后面的所有人。

lock_timeout

控制语句等待锁多久后报错的设置。设为几秒并配合重试,就能把迁移引起的锁队列停机变成一个无害的错误。参见规则一:lock_timeout 和重试。

逻辑解码(Logical decoding)

将 WAL 转换为行变更流的机制,通过逻辑复制槽和输出插件读取。逻辑复制建立在它之上,变更数据捕获工具也是如此。参见逻辑复制拷贝行,不拷贝页面。

逻辑复制(Logical replication)

复制行变更而非页的复制方式:发布端将 WAL 解码为行的插入和更新,由可写的订阅端应用。用于升级、选择性同步以及迁出服务商。参见逻辑复制拷贝行,不拷贝页面。

丢失更新(Lost update)

两个事务读取同一个值、各自计算出新值,而第二次写入悄悄覆盖第一次写入的异常。应在 update 内完成运算,或先锁住该行。参见Read committed 会丢失在应用中计算的更新。

日志序列号(LSN)

日志序列号(log sequence number):无限延伸的 WAL 流中的字节偏移,写作 2/8BE4C8B0 这样的形式。两个 LSN 相减,就是它们之间的 WAL 字节数。参见LSN 是字节偏移量。

轻量级锁(LWLock)

保护共享内存、持有时间以微秒计的轻量级锁。大量 LWLock 等待意味着太多会话在做同一种内部操作,而不是有人锁住了你的行。参见解决 LWLock 等待要调整负载,而不是查询。

M

物化视图(Materialized view)

以表的形式保存的查询结果。普通 refresh 执行期间会阻塞读取;refresh ... concurrently 不会,但需要唯一索引。两种方式都要重新计算全部结果,因此对持续增长的数据应使用汇总表。参见物化视图会全部重算。

高频值列表(MCV list)

列中最常见的值及各自的出现频率(most_common_vals 和 most_common_freqs)。对于这些值的等值过滤,规划器直接依据列表估算。参见pg_stats 是规划器看到的全部信息。

Memoize

一种执行计划节点(PostgreSQL 14+),按连接值缓存嵌套循环的内侧结果,相当于带记忆的嵌套循环。Hits 是它省下的查找次数。参见交叉点、归并连接和 Memoize。

归并连接(Merge join)

按连接键顺序同时遍历两侧输入的连接方式,就像合并两个有序列表。当两侧通过索引输出、已经有序时,代价很低。参见每种连接方法只适合某些行数。

多事务 ID(Multixact)

多个事务同时锁住一行时,写入该行 xmax 的 ID,指向持锁者列表。对同一条父表记录并发检查外键,会迅速产生大量多事务 ID;它们也有自己的回卷限制。参见共享行锁会产生 multixact,而 multixact 也会回卷。

多版本并发控制(MVCC)

多版本并发控制(multiversion concurrency control):在这种设计下,Postgres 从不原地覆盖行,因此读取者无需等待写入者。更新就是插入,膨胀和清理也由此而来。参见更新就是插入。

N

N+1 查询(N+1 queries)

先用一次查询取列表,再对每个条目分别查询一次的模式,通常藏在 ORM 的循环里。每次查询都很快,但往返开销会累积。用连接或一个数组参数来解决。参见N+1 是一个循环,不是慢查询。

嵌套循环(Nested loop)

对外侧每一行都执行一次内侧操作的连接方式。外侧很小、内侧是索引查找时效果很好;外侧行数估错时则会很糟。参见每种连接方法只适合某些行数。

网络块存储(Network block storage)

通过网络访问、按 IOPS 和吞吐量计费的云磁盘:持久可靠、可以分离,但每个未命中内存的页都意味着向远端存储服务器发出一次请求。本地 NVMe 快得多,但会随主机丢失。参见存储决定基准测试结果。

节点(Node)

查询计划中的一个步骤:扫描、连接、排序或聚合,从下层节点拉取行,再向上层传递行。读计划要从缩进最深的节点向外读。参见计划是一棵树;从里向外读取。

NOT VALID

添加约束时不检查已有行的选项,只短暂持锁;之后再用 validate constraint 在较弱的锁下检查已有行。这是在大表上添加外键和检查约束的安全方式。参见先添加 NOT VALID 外键,再验证。

O

在线事务处理(OLTP)

在线事务处理(online transaction processing):大量短事务,每个只读写少量行,正是 Postgres 的行式存储和 B-树所针对的负载。分析查询扫描许多行、却只使用少量列,是列式存储占优的场景。参见行式存储为了用两列,必须读取所有列。

操作符类(Operator class)

索引定义中说明某种类型如何比较、索引能支持哪些操作符的部分。text_pattern_ops 让 B-树在语言相关的排序规则下也能支持 like 'abc%';jsonb_path_ops 则是用于包含关系查询、体积更小的 GIN 操作符类。参见前缀查询需要正确的操作符类。

对象关系映射(ORM)

对象关系映射器(object-relational mapper),将对象转换为 SQL 的库。典型应用发送的大部分查询都由它生成,因此要阅读它生成的 SQL:N+1 循环和重写所有列的更新都源于这里。参见N+1 是一个循环,不是慢查询。

操作系统页缓存(OS page cache)

内核利用空闲内存缓存的文件内容,位于 shared_buffers 之下的第二层缓存。执行计划中的 read 可能只花几微秒就从这里完成,热点页也可能同时存在于两层缓存中(双重缓冲)。参见一页可能位于三种位置之一。

事务发件箱(Outbox)

与业务变更在同一事务中写入的表,保存应通知其他系统的消息。转发程序发布消息并删除已发送的记录,因此不可能出现提交了业务变更却没有对应消息,或有消息却没有对应提交的情况。参见把消息和订单放进同一个事务。

P

页(Page)

Postgres 读取、写入、缓存和记入日志的 8 KB 单位:页头、从前端排列的行指针、从后端排列的元组,以及两者之间的空闲空间。查询的代价主要取决于它访问多少个页。参见每个页都有相同的布局。

并行查询(Parallel query)

将大型扫描或聚合分给 Gather 节点下的多个工作进程的执行计划。它让一条查询更早完成,读取的页数却相同,因此消耗的服务器资源更多,而不是更少。参见并行查询更快,但不更便宜。

部分索引(Partial index)

只为满足其 where 子句的行建立的索引,例如尚未处理的任务或存活行。它体积小、维护代价低,但查询条件必须蕴含该谓词才能使用。参见只为要查询的行建索引,而不是整张表。

分区(Partition)

分区表的一张子表,保存分区键落在其范围或列表中的行。每个分区都有自己的堆、索引和清理安排,因此执行保留策略时,只需快速删除整张子表。参见所有收益都来自拆成独立的表。

分区剪枝(Partition pruning)

在规划时或执行时跳过不可能包含匹配行的分区的步骤。只有查询按分区键过滤时才会发生。不要与清除单个页内死元组的页内剪枝混淆。参见分区边界是免费的粗粒度索引。

pgbench

Postgres 自带的基准测试工具。它按你指定的客户端数运行 SQL 脚本,并报告吞吐量和延迟。内置的类 TPC-B 测试主要衡量磁盘的 fsync,因此应编写贴近实际负载的脚本。参见pgbench 已经安装好了。

PgBouncer

外部连接池:用一个事件驱动的进程,将少量、固定数量的服务器连接借给众多客户端。应与应用自身的连接池配合使用。参见在应用中并在数据库前面使用连接池。

pg_stat_statements

汇总每条规范化语句的调用次数、耗时、行数和页数的扩展。寻找昂贵操作,应从这里开始。参见从 pg_stat_statements 开始。

时间点恢复(PITR)

时间点恢复(point-in-time recovery):还原基础备份,然后重放归档 WAL(每个 16 MB 段写满后就复制出去),直到误操作发生之前。参见时间点恢复就是持续重放。

连接池模式(Pooling modes)

连接池借出服务器连接的三种方式。会话池在整个会话期间保留连接。事务池只在事务内保留连接:收益最大,但会破坏 set 等会话状态。语句池禁止包含多条语句的事务。参见事务池模式会破坏会话状态。

Postmaster

Postgres 启动的第一个进程,也是所有其他进程的父进程。它监听连接,为每个连接启动一个后端进程,并启动后台进程。要避免它被 OOM 杀手终止。参见每个连接都是一个进程。

页内剪枝(Pruning)

查询读取一个接近填满的页时,顺手清除页内死元组的操作:代价低,每次只处理一页,不处理索引。完整清理工作由 VACUUM 完成。不要与跳过整个分区的分区剪枝混淆。参见页接近填满时才进行页内剪枝。

R

Recall@k

真正最近的 k 个邻居中,被近似搜索返回的比例。Recall@10 为 0.9,意味着真正最近的十个邻居返回了九个;近似索引用召回率换速度。参见ef_search 用延迟换召回率。

倒数排名融合(Reciprocal rank fusion)

合并评分尺度不同的排名的方法,用于文本与向量的混合搜索:每份返回某文档的列表,都为它贡献 1 / (60 + rank) 分,各项之和决定排序。参见关键词和向量的失败方式不同,所以要融合。

重定向行指针(Redirect)

指向同一页上另一个行指针的行指针,在页内剪枝或清理移除 HOT 链头时留下。索引仍指向旧槽位,由重定向行指针继续转向目标。参见只有没有快照能看到的版本才是死版本。

复制槽(Replication slot)

主库记录某个消费者已读取到哪里,并承诺保留它仍然需要的 WAL。废弃的复制槽会一直保留 WAL,直到撑满磁盘。参见复制槽承诺保留 WAL。

汇总表(Rollup)

存储聚合结果的表,例如每个账户每天的事件数;新行到来时持续更新,通常通过幂等的插入或更新实现。仪表盘读取汇总表而非原始行:在我们的测试中,只读 4 个页,而不是 35,249 个。参见预先算好要展示的内容。

行级安全(Row-level security)

Postgres 添加到针对某张表的每次查询中的策略;策略条件不为真的行无法读取或写入。将租户记录在会话设置中,即使应用忘记了 where,策略仍然有效。参见策略会让其他租户的行不可见。

RPO 与 RTO(RPO and RTO)

恢复点目标,表示你能容忍丢失多少数据,以时间衡量;恢复时间目标,表示你能容忍多长的停机恢复时间。选择备份方法前,先写下这两项目标。参见先写下 RPO 和 RTO。

S

可利用索引的(Sargable)

将未经包装的列与扫描前已知的值相比较的谓词,因此可以利用索引。如果给列套上函数或类型转换,索引就帮不上忙了。参见保持索引列裸露。

保存点(Savepoint)

事务内一个带名字的位置,可以回滚到这里,而不丢掉整个事务,用于限制单条语句失败造成的影响。保存点内一旦写入,就会分配独立的子事务,因此不要在循环中使用。参见保存点可以把损害控制住。

search_path

Postgres 查找未限定名称时依次搜索的模式。以所有者权限运行的 security definer 函数必须固定它,否则调用者就可能放入一个以函数所有者身份执行的对象。参见较旧的数据库仍允许任何人在 public 中创建对象。

选择率(Selectivity)

规划器预计过滤后会保留的行的比例,依据高频值列表、直方图或兜底估算得出。后续每项选择都建立在它之上。参见手算规划器的算术。

顺序扫描(Sequential scan)

按物理顺序读取表中每个页,并逐行检查过滤条件的扫描方式。需要表中很大一部分数据时,它是合适的计划;只要少量行时则不合适,此时会表现为 Seq Scan 配上很大的 Rows Removed by Filter。参见Filter 表示行已经被读出并丢弃。

可串行化(Serializable)

最严格的隔离级别:结果必须等价于某种逐个执行事务的顺序,否则某个事务会以 SQLSTATE 40001 失败,由你的代码重试。参见Serializable 能捕获 repeatable read 漏掉的写偏差。

分片(Shard)

由独立主库承载的一部分行。分片键决定每一行存在哪里;查询若没有指定分片键,就会发往每个分片。参见分片让一切成倍增加。

shared_buffers

Postgres 在共享内存中维护的页缓存。默认大小为 128 MB;专用服务器可从物理内存的约 25% 起步。参见shared_buffers 是一个页数组。

SKIP LOCKED

一个行锁选项:遇到其他事务锁住的行时跳过,而不等待。它让多个工作进程可以从同一张队列表领取任务,互不阻塞。参见Postgres 队列与业务写入一起提交。

跳跃扫描(Skip scan)

PostgreSQL 18 在未指定多列 B-树首列时使用该索引的方式:首列的每个值各沿树向下查找一次。首列不同值很少时有效,但不要围绕它来做设计。参见先等值,再范围或排序。

快照(Snapshot)

在语句或事务开始时,记录哪些事务已经提交。行版本若在快照之前诞生、到那时还没有死亡,就是可见的。参见一个快照选择一个版本。

SP-GiST

空间分区 GiST(Space-partitioned GiST),适用于能划分为互不重叠区域的数据(前缀、点、IP 网络),搜索因此可以沿树中的一条路径向下进行。参见你很少会需要 SP-GiST。

脑裂(Split brain)

两台服务器同时作为主库接受写入,通常是因为故障切换提升了副本,却没有隔离旧主库。两边的历史会分叉,而且没有机制合并。应使用能隔离旧主库的故障切换管理工具,绝不要自行写脚本。参见不要自己拼凑故障切换。

可串行化快照隔离(SSI)

可串行化快照隔离(serializable snapshot isolation),Postgres 实现可串行化的方式:在可重复读的快照上,额外跟踪读写依赖;一旦形成危险的环,就中止一个事务。无需阻塞任何事务,但有事务需要重试。参见serializable 如何在不阻塞任何人的情况下捕获它。

statement_timeout

控制一条语句可以运行多久、超时后即取消的设置。应设得比请求截止时间更短,让已被放弃的工作不再继续持锁。参见每个超时默认都是关闭的。

流复制(Streaming replication)

备库随主库 WAL 到来而重放,形成逐字节一致、可响应读取的副本(热备库)。默认异步,因此故障切换可能丢失最后几次提交。参见副本是一台始终处于恢复中的服务器。

子事务(Subtransaction)

保存点或 PL/pgSQL 的 exception 块发生写入时取得的事务 ID。每个后端进程缓存 64 个;超过后,可见性检查就要回到磁盘查找。参见子事务并非没有代价。

synchronous_commit

控制提交要等待什么的设置。设为 off 时,提交会在 WAL 刷新前返回;崩溃可能丢失一个短时间窗口内、其他事务已经见过的事务,但不会造成数据损坏。参见synchronous_commit = off 会丢失一个窗口。

同步复制(Synchronous replication)

每次提交都等待备库确认 WAL 的复制方式,因此故障切换不会丢失数据。每次提交都要付出一次网络往返的代价;如果备库不可用,写入就会停止。参见同步提交要付出一次往返。

T

租户(Tenant)

多租户应用中的一个客户账户,多个客户共用同一批表。本书在每张租户表上都设置 account_id,并将它放在键的开头,让查询、行级安全以及将来的分片键保持一致。参见让租户无处不在。

文本搜索配置(Text search configuration)

解析器与每种词元所经过的词典的组合,例如 'english'。它是索引所存内容的一部分,因此始终应显式传入。参见配置是索引的一部分。

时间线(Timeline)

WAL 历史中带编号的分支。每次提升都会开启一条新时间线,WAL 文件名中包含其编号,因此恢复过程和 pg_rewind 能分辨一个段属于哪段历史。参见一次调用提升副本,开启新时间线。

TOAST

超长属性存储技术(The Oversized-Attribute Storage Technique):当一行超过约 2 KB 时,大字段值会被压缩,必要时分块移到附属表中。修改一个经过 TOAST 处理的值,会重写整个值。参见大值会先压缩,再移出行外。

事务 ID(Transaction ID)

事务首次写入时取得的 32 位编号(XID);只读事务从不取得这一编号。行版本中会记录事务 ID,因此计数器回卷至关重要。参见每次写入都会取得一个事务 ID。

触发器(Trigger)

表发生变化时,数据库运行的函数:可以在每行变化之前或之后执行,也可以每条语句执行一次。它对每个写入者都会触发,这正是它的意义;行级触发器即使几乎什么也不做,每行也要花费约一微秒。外键同样由触发器强制执行。参见触发器逐行运行,除非你另有指定。

三字符组(Trigram)

字符串中连续三个字符组成的序列。pg_trgm 扩展通过 GIN 或 GiST 为每个三字符组建立索引,让 like '%x%' 和相似度搜索可以利用索引。参见pg_trgm 让前置通配符也能使用索引。

tsvector

全文搜索类型:带位置信息、按顺序排列的词素,与 tsquery 通过 @@ 匹配。应将它存入一列,并在该列上建立索引。参见存储 tsvector;不要索引表达式。

元组(Tuple)

存储在页上的一个行版本。更新会写入新元组;旧元组会一直保留,直到死亡并被清理。参见每个页都有相同的布局。

元组头(Tuple header)

每个元组开头的 23 字节:xmin、xmax、ctid(自身或更新版本的地址)以及标志位;若有空值位图,则紧随其后。参见一行的成本高于它的列。

两阶段提交(Two-phase commit)

跨多个数据库的一笔事务:各方先准备,再全部提交。锁会一直持有到第二阶段;如果协调者在两阶段之间故障,就会留下已准备的事务。参见两阶段提交很少是答案。

TypeID

带前缀 ID 的一种规范:小写类型前缀、下划线,再接 base32 编码的 UUIDv7,例如 prj_01j6b7yh69fq2vyabwvmba4nr5。存储 uuid,显示时再带上前缀。参见前缀让 ID 说明自己的类型。

U

ULID

一个 128 位 ID,布局类似 UUIDv7(48 位毫秒时间戳、80 位随机数),没有版本位,以 26 个 Crockford base32 字符表示。它可以逐字节放入 uuid 列。参见ULID 与 uuid 列逐字节匹配。

不记日志表(Unlogged table)

不写 WAL 的表:写入便宜得多,崩溃后会被清空,也不会复制。适合暂存和可丢弃的数据。参见崩溃后,不记日志表会被清空。

插入或更新(Upsert)

键已存在时改为更新已有行的插入操作,以一条原子语句完成:insert ... on conflict。它指定用于裁决冲突的唯一索引,在并发下也是安全的。参见ON CONFLICT 是安全的插入或更新。

UUIDv4

包含 122 个随机位的 UUID。随机键可能落到 B-树的任何位置;在我们的测试中,100 万个这样的键写出了 551 MB WAL,而 UUIDv7 为 143 MB。参见UUIDv4 隐藏创建时间,但 WAL 是 3.8 倍。

UUIDv7

以 48 位毫秒时间戳开头的 UUID,因此新键落在索引最右端。自 PostgreSQL 18 起内置 uuidv7();它会暴露行的创建时间。参见UUIDv7 是默认选择,而且会泄露创建时间。

V

VACUUM

维护命令,用于移除死元组及其索引条目、记录空闲空间、截断尾部空页、冻结旧元组,并维护可见性映射。它让空间可以复用,很少会缩小文件。参见清理要做五件事。

VACUUM FULL

将表重写为一个新的紧凑文件的命令,在整个运行期间持有 ACCESS EXCLUSIVE。应安排维护窗口,或使用在线重建工具。参见大表执行 VACUUM FULL 会造成停机。

可见性映射(Visibility map)

每个堆页对应两个位:全可见和全冻结。仅索引扫描和清理依据它们跳过页;对页的任何修改都会清除它们。参见每页两位让扫描和清理跳过工作。

易变性(Volatility)

函数向规划器给出的承诺:immutable、stable 或 volatile(默认值)。错误地声明为 immutable 会返回过时的结果。参见易变性是给规划器的承诺。

W

等待事件(Wait event)

pg_stat_activity 中显示的会话正在等待的对象:Lock、LWLock、IO 或 Client。对这些事件采样,就能了解时间花在哪里。参见没有等待事件的活动会话正在占用 CPU。

预写式日志(WAL)

预写式日志(write-ahead log):在写入数据页之前,先将每次变更作为紧凑记录追加进去。提交只需等待日志;恢复、复制和时间点恢复都通过重放它完成。参见先写日志,而不是写数据页。

工作集(Working set)

负载在某段时间(例如一小时)内访问的页:是实际使用的那部分,而非整个数据库。应按工作集配置内存,超过它再加内存也没有收益。参见根据工作集配置 RAM。

work_mem

一个排序或哈希节点在溢写到临时文件之前可以使用的内存,默认 4 MB。它按节点、按连接分别计算,因此调高前先算乘法。参见work_mem 是按节点计算的,而且可以看到。

回卷(Wraparound)

32 位事务 ID 计数器转过一整圈、追上未冻结行的时刻,旧行会突然看起来像来自未来。Postgres 会在此之前停止接受写入,因此冻结必须跟上。参见回卷会在任何内容损坏前停止写入。

写偏差(Write skew)

两个事务读取相同数据、各自修改不同的行,合起来却破坏了一条各自单独执行时都不会破坏的规则的异常。只有可串行化级别能捕获它。参见Serializable 能捕获 repeatable read 漏掉的写偏差。

X

xmin 与 xmax

每个元组头中的两个事务 ID:xmin 插入了这个版本;xmax 删除、替换或锁住了它(均未发生时为 0)。将它们与快照对照,就能决定谁能看到这行。参见每次写入都会取得一个事务 ID。

xmin 清理界限(xmin horizon)

仍在使用的最老快照。早于它死亡的版本可以移除,其余必须保留。因此,一笔长事务会拖住整个数据库的清理;复制槽则会拖住整个集簇的清理。参见长事务会把一切都拖住。