表空间与性能(Tablespace and Performance)
上一节讲了表空间怎么建、怎么用、怎么下线。这一节回答一个更实际的问题:把表放到不同的盘上,到底能不能变快?
答案分两半。一半是「把数据放到更快的盘上」——这是硬件层面的事,表空间只是决定了数据落在哪块设备上;另一半是「让规划器知道这块盘有多快」——这是软件层面的事,通过表空间级代价参数做到。只做前一半,规划器仍然会按「所有盘都一样慢」来估算,你可能白白浪费了一块 SSD。
本页聚焦后半部分:表空间级代价参数的语义、如何影响执行计划,以及冷热分层在分区表上的落地写法。所有参数与默认值均以 PostgreSQL 18 为准。
前置知识
阅读本节前,建议先了解:表空间创建与使用、Tablespace(表空间原理)、分区裁剪(Partition Pruning)
基础概念
表空间本身不产生性能
表空间是一个目录别名,它只做一件事:告诉 PostgreSQL「这个对象的文件放在哪个目录下」。它不缓存数据、不压缩数据、不改变页格式、不加索引。
所以,「用了表空间,查询就快了」这句话本身是错的。真正发生的是:
- 你把某个表放到了读带宽更高、随机寻址更快的设备上(比如 SSD);
- 或者你把互相争抢 I/O 的对象分到了不同的物理设备上(比如数据、索引、临时文件各占一块盘);
- 或者你只是把一个不常访问的表挪到便宜的大容量 HDD 上,给热数据腾出空间。
这三种收益都来自物理布局的变化,而不是表空间这个抽象本身。表空间只是让物理布局变得「可选择」。
规划器默认不知道你的盘快还是慢
PostgreSQL 的查询规划器基于代价模型挑选执行计划。代价不是时间,而是一个无量纲的相对值,基准是「顺序读一页」:
seq_page_cost默认1.0——顺序读一页的代价;random_page_cost默认4.0——随机读一页的代价,是顺序读的 4 倍。
这个 4.0 是一个面向机械硬盘的保守假设。在 SSD 上,随机读和顺序读的差距远远不到 4 倍,甚至接近 1 倍。规划器并不知道你的表在哪块盘上,它只会用同一个 4.0 去估算所有索引扫描的代价——结果是在 SSD 上严重低估了索引扫描的性价比,可能错误地选择全表扫描。
表空间级代价参数:给每块盘单独标价
PostgreSQL 15 引入了表空间级参数,允许你为某个表空间单独覆盖这四个 I/O 相关参数。这样,规划器在估算「访问某个表/索引的代价」时,会自动用该对象所在表空间的参数,而不是全局值。
官方原文:
Setting these values for a particular tablespace will override the planner's usual estimate of the cost of reading pages from tables in that tablespace, and how many concurrent I/Os are issued, as established by the configuration parameters of the same name... This may be useful if one tablespace is located on a disk which is faster or slower than the remainder of the I/O subsystem.
这段话说清了表空间级参数的价值:当某块盘明显比集群里其他盘快(或慢)时,用它来修正规划器的估算。
语法与配置
表空间级参数可以在创建时写入,也可以用 ALTER TABLESPACE 事后设置或重置。可设置的参数只有四个。
-- 创建表空间时直接带上参数(PostgreSQL 15+)
CREATE TABLESPACE tablespace_name
LOCATION 'directory'
WITH ( tablespace_option = value [, ... ] );
-- 事后设置(只覆盖列出的参数,其余保持原值)
ALTER TABLESPACE tablespace_name
SET ( tablespace_option = value [, ... ] );
-- 重置为全局默认值
ALTER TABLESPACE tablespace_name
RESET ( tablespace_option [, ... ] );
-- 表空间的其余两种用法(本页不展开)
ALTER TABLESPACE tablespace_name RENAME TO new_name;
ALTER TABLESPACE tablespace_name OWNER TO new_owner;四个可用的 tablespace_option:
| 参数 | 默认值 | 类型 | 含义 |
|---|---|---|---|
seq_page_cost | 1.0 | 浮点 | 顺序读一页的代价 |
random_page_cost | 4.0 | 浮点 | 随机读一页的代价 |
effective_io_concurrency | 16(PG 18 起) | 整数 | 该盘上并发 I/O 的期望数量 |
maintenance_io_concurrency | 16(PG 18 起) | 整数 | 维护操作(VACUUM、建索引)的并发 I/O 数 |
查看某个表空间上生效的参数覆盖:
-- spcoptions 只显示被显式覆盖的项,未列出的一律走全局默认值
SELECT spcname, spcoptions
FROM pg_tablespace
WHERE spcname = 'ts_ssd'; spcname | spcoptions
---------+-----------------------------------------------------
ts_ssd | {random_page_cost=1.1,effective_io_concurrency=200}表空间级参数是 PostgreSQL 15 才引入的
在 PostgreSQL 14 及更早版本上,CREATE TABLESPACE ... WITH (...) 与 ALTER TABLESPACE ... SET (...) 都会直接报语法错误,只能改全局参数。表空间创建与使用 中说明过这一点,这里再次强调,因为本节的全部写法都依赖 15+。
详细说明
seq_page_cost:顺序读一页的相对代价
seq_page_cost 是代价模型的基准单位,默认固定为 1.0。它不直接决定选不选索引,但它决定了「一次全表扫描要花多少代价」:
顺序扫描的总 I/O 代价 ≈ 表的页面数 × seq_page_cost官方对它的描述很简短:
Sets the planner's estimate of the cost of a disk page fetch that is part of a series of sequential fetches. The default is 1.0. This value can be overridden for tables and indexes in a particular tablespace by setting the tablespace parameter of the same name.
实际调优中很少单独改 seq_page_cost。 因为它一旦改动,所有表空间的代价基准都会漂移,类比困难。常见做法是把它固定在 1.0,只调 random_page_cost。
random_page_cost:决定索引扫描的性价比
random_page_cost 是最值得调、也最容易调错的参数。它直接决定规划器愿不愿意走索引。
官方原文:
Sets the planner's estimate of the cost of a non-sequentially-fetched disk page. The default is 4.0.
Reducing this value relative to
seq_page_costwill cause the system to prefer index scans; raising it will make index scans look relatively more expensive.
方向很明确:调小 → 更爱用索引;调大 → 更爱全表扫描。
官方还专门解释了「为什么机械盘上是 4.0 而不是更高」:
Random access to durable storage is normally much more expensive than four times sequential access. However, a lower default is used (4.0) because the majority of random accesses to storage, such as indexed reads, are assumed to be in cache. Also, the latency of network-attached storage tends to reduce the relative overhead of random access.
也就是说,4.0 已经隐含了「大部分随机读命中缓存」这一前提。在全内存命中或 SSD/NVMe 场景下,这个值仍然偏高。官方的调优建议:
If you believe caching is less frequent than the default value reflects, and network latency is minimal, you can increase
random_page_costto better reflect the true cost of random storage reads. Storage that has a higher random read cost relative to sequential, like magnetic disks, might also be better modeled with a higher value forrandom_page_cost. Correspondingly, if your data is likely to be completely in cache, such as when the database is smaller than the total server memory, or network latency is high, decreasingrandom_page_costmight be appropriate.
一个容易被忽略的官方提醒
官方在 Tip 中明确写道:Although the system will let you set random_page_cost to less than seq_page_cost, it is not physically sensible to do so. However, setting them equal makes sense if the database is entirely cached in RAM, since in that case there is no penalty for touching pages out of sequence.
也就是说:允许设成小于 seq_page_cost,但物理上说不通;如果整个数据库都常驻内存,把两者设为相等是合理的。实践中 SSD 表空间常见的取值区间是 1.0 ~ 2.0,全闪存且数据量小于内存时可以直接取 1.0。
effective_io_concurrency:并发 I/O 的期望数量
这个参数控制该盘上能同时发起的 I/O 请求数量。它不改变单个 I/O 的代价,而是告诉执行器「这块设备可以并行处理多少个读请求」。
官方描述(PostgreSQL 17 及更早版本):
Currently, this setting only affects bitmap heap scans.
PostgreSQL 18 扩大了它的作用范围
PostgreSQL 18 引入了异步 I/O(AIO)子系统,官方概述中写道:An asynchronous I/O (AIO) subsystem that can improve performance of sequential scans, bitmap heap scans, vacuums, and other operations.
也就是说,在 PG 18 上,并发 I/O 不再局限于位图堆扫描,顺序扫描、VACUUM 等操作都能受益。因此本页不照搬旧版本「只影响 bitmap heap scan」的表述,而是按「影响支持 AIO 的读操作」来理解。具体覆盖范围请以你实际使用版本的官方文档为准。
取值方向:
- 机械硬盘:通常设
1~2(并发寻道反而更慢); - SATA SSD:几十的量级;
- NVMe SSD:可以设到
100~200甚至更高。
设置过高不会「损坏」什么,但会无谓地增加队列压力。
maintenance_io_concurrency:维护操作的并发 I/O
和 effective_io_concurrency 同源,但作用于维护操作:VACUUM、CREATE INDEX、ANALYZE 等。
-- 让这块 NVMe 上的维护操作充分利用并发 I/O
ALTER TABLESPACE ts_nvme SET (maintenance_io_concurrency = 64);它与全局参数 maintenance_io_concurrency 的关系,和 effective_io_concurrency 完全一致:表空间级的值覆盖全局值。
为什么只有这四个参数能在表空间上设置
官方的 tablespace_option 只有四个值。像 work_mem、effective_cache_size、cpu_tuple_cost 这些参数不能在表空间上设置。
原因很直观:表空间表达的是「这块盘怎么访问」,所以能覆盖的只有与设备 I/O 特性相关的参数。而 work_mem 是会话级内存,cpu_tuple_cost 是 CPU 代价,它们和「数据放在哪块盘上」没有关系,自然也就没有表空间级的版本。
理解这个边界,可以避免写出无效配置:
-- 错误!tablespace_option 只有四个值,这里会直接报错
ALTER TABLESPACE ts_ssd SET (effective_cache_size = '32GB');ERROR: unrecognized parameter "effective_cache_size"PostgreSQL 18 的 AIO 与新的默认值
PostgreSQL 18 对 I/O 子系统做了两处重要改动。
第一,新默认值。 PG 18 发布说明原文:
Increase server variables
effective_io_concurrency's andmaintenance_io_concurrency's default values to 16 (Melanie Plageman)... This more accurately reflects modern hardware.
即 effective_io_concurrency 与 maintenance_io_concurrency 的默认值都由旧值提升到了 16。这意味着即使你不做任何配置,PG 18 在现代硬件上也会比旧版本更积极地发起并发 I/O。
第二,新增 AIO 配置。 PG 18 引入 io_method 参数,可取以下值:
worker(默认)——用后台工作进程执行异步 I/O;io_uring——用 Linuxio_uring执行,需要编译时带有--with-liburing;sync——退回旧的同步 I/O 行为。
-- 查看当前 AIO 方法
SHOW io_method; io_method
-----------
worker
(1 row)AIO 目前只作用于读操作
PostgreSQL 18 的 AIO 只用于读路径,且主要是顺序扫描、位图堆扫描、VACUUM 等。写入路径仍然是同步的。所以不要期望开 io_uring 就能提升所有负载。
表空间级参数如何影响执行计划
理解参数语义之后,关键是看它怎么改变计划。代价参数影响的是相对性价比:
索引扫描代价 ≈ 随机读的页面数 × random_page_cost + CPU 代价
顺序扫描代价 ≈ 表的页面数 × seq_page_cost + CPU 代价把某个表空间的 random_page_cost 从 4.0 降到 1.1,该表空间上所有表的索引扫描代价被显著压低,规划器就会更倾向于走索引。
下面用一组示意数据说明(真实代价数值随表大小、统计信息变化,此处仅示趋势):
-- 同一个查询:按 user_id 过滤,走同一个索引,只是表所在表空间不同
EXPLAIN (COSTS ON) SELECT * FROM orders WHERE user_id = 42;-- 情况 A:表空间 ts_hdd,random_page_cost = 4.0(默认)
QUERY PLAN
---------------------------------------------------------------------------
Index Scan using orders_user_id_idx on orders (cost=0.43..24.30 rows=10 width=120)
Index Cond: (user_id = 42)
(2 rows)-- 情况 B:表空间 ts_ssd,random_page_cost = 1.1
QUERY PLAN
---------------------------------------------------------------------------
Index Scan using orders_user_id_idx on orders (cost=0.43..18.55 rows=10 width=120)
Index Cond: (user_id = 42)
(2 rows)索引部分的代价下降了,而顺序扫描的代价不受影响。当二者接近时,这个差值就足以让规划器在「索引扫描」和「顺序扫描」之间翻转选择。这正是表空间级参数最有价值的地方。
表空间不是缓存,别把两件事混为一谈
一个常见误解:调了 random_page_cost = 1.1,就以为数据会常驻内存。
不会。代价参数只影响规划器的估算,不影响任何实际的内存分配。真正决定缓存的参数在别处:
| 参数 | 作用域 | 说明 |
|---|---|---|
shared_buffers | 全局、需重启 | PostgreSQL 自己的共享缓冲池 |
effective_cache_size | 全局 | 仅用于估算,告诉规划器「操作系统缓存里大致有多少可用」 |
seq_page_cost / random_page_cost | 全局 + 表空间 | 估算单页读取代价 |
其中 effective_cache_size 默认 4GB,官方明确强调它不预留任何内存:This parameter has no effect on the size of shared memory allocated by PostgreSQL, nor does it reserve kernel disk cache; it is used only for estimation purposes.
实际调优时的合理组合是:用表空间级参数修正「盘的速度」,用 effective_cache_size 修正「缓存命中率」,两者配合才能让规划器的估算贴近真实。
什么情况下表空间根本帮不上忙
在投入调优之前,先排除这些情况——它们不是调参数能解决的:
- 瓶颈在 CPU 或锁,不在 I/O。 表空间参数只影响 I/O 相关代价,对
cpu_tuple_cost、锁等待无能为力。 - 所有盘其实是同一块物理盘。 官方提醒过,在同一个逻辑文件系统上建多个表空间通常没有意义——它们仍然争抢同一份 I/O 资源。
- 热数据量远小于内存。 如果热数据已经全部命中缓存,物理盘的速度差异体现不出来,调
random_page_cost的收益也很有限。 - 瓶颈是单条 SQL 的算法问题。 缺失索引、错误的连接方式,靠代价参数是「碰运气」而不是解决。
冷热分层的核心收益是「让出资源」
冷热分层最常见的收益并不是「冷数据变快了」,而是「热数据变快了」——因为冷数据不再占用热数据的缓存和 I/O 带宽。
┌──────────────────────────────┐
│ 分区表 orders │
└──────────────┬───────────────┘
│
┌──────────────────────┴──────────────────────┐
│ │
orders_2026_09 .. orders_2026_10 orders_2025_01 ..
(近 3 个月,热) (历史,冷)
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│ ts_ssd │ random_page_cost = 1.1 │ ts_cold │ 默认代价
│ NVMe │ effective_io_concurrency = 200 │ HDD │ 大容量、便宜
└───────────┘ └───────────┘
│ │
▼ ▼
单分区索引扫描极快 很少被访问,无需索引扫描关键点:
- 参数要跟着盘走。 热分区所在的
ts_ssd需要显式设置代价参数;冷分区保持默认即可(HDD 上random_page_cost = 4.0反而是合适的)。 - 父表上的
SET TABLESPACE不搬数据。 它只设定「未来新分区的默认表空间」,已有分区必须逐个迁移(见 表空间创建与使用)。 - 分区裁剪让冷分区不参与扫描。 只要查询条件带上分区键,规划器就能直接跳过冷分区,
ts_cold上的表几乎不会出现在执行计划里——这正是 分区裁剪 与冷热分层的配合点。
实战示例
场景一:SSD + HDD 混合部署,让规划器知道 SSD 更快
假设集群里有两块盘:一块 NVMe(快),一块大容量 HDD(慢)。目标是把热数据放 NVMe,并让规划器知道这块盘很快。
-- 第一步:为快盘建表空间,并直接标注它的速度特性
CREATE TABLESPACE ts_ssd
LOCATION '/data/nvme/pg_tblspc'
WITH (random_page_cost = 1.1, effective_io_concurrency = 200);
-- 第二步:为慢盘建表空间,保持默认代价(4.0 对 HDD 是合适的)
CREATE TABLESPACE ts_hdd
LOCATION '/data/hdd/pg_tblspc';-- 第三步:确认参数已生效
SELECT spcname, spcoptions FROM pg_tablespace WHERE spcname LIKE 'ts_%'; spcname | spcoptions
---------+------------------------------------------------------------
ts_hdd |
ts_ssd | {random_page_cost=1.1,effective_io_concurrency=200}注意 ts_hdd 的 spcoptions 是空的——没有覆盖就是使用全局默认值,这是正常的,不需要显式写 random_page_cost=4.0。
场景二:用 EXPLAIN 验证参数是否真的生效
设置完参数后不要想当然,用 EXPLAIN 对照验证。
-- 在两个表空间各建一张结构相同的表
CREATE TABLE t_ssd (id int, payload text) TABLESPACE ts_ssd;
CREATE TABLE t_hdd (id int, payload text) TABLESPACE ts_hdd;
INSERT INTO t_ssd SELECT g, 'x' FROM generate_series(1, 100000) g;
INSERT INTO t_hdd SELECT g, 'x' FROM generate_series(1, 100000) g;
CREATE INDEX ON t_ssd (id);
CREATE INDEX ON t_hdd (id);
ANALYZE t_ssd;
ANALYZE t_hdd;-- 对比同一个查询在两块表空间上的代价估算
EXPLAIN (COSTS ON) SELECT * FROM t_ssd WHERE id = 50000;
EXPLAIN (COSTS ON) SELECT * FROM t_hdd WHERE id = 50000;-- t_hdd(random_page_cost = 4.0):索引页 + 堆页各一次随机读 → 约 2 × 4.0
Index Scan using t_hdd_id_idx on t_hdd (cost=0.29..8.30 rows=1 width=10)
-- t_ssd(random_page_cost = 1.1):同样两次随机读 → 约 2 × 1.1
Index Scan using t_ssd_id_idx on t_ssd (cost=0.29..2.50 rows=1 width=10)计划类型完全相同(都是索引扫描),但估算代价差了约 3.3 倍——因为单行查询也要随机读一次索引页和一次堆页,而这两次读的单价都被表空间级参数改写了。直观理解:
索引扫描代价 ≈(索引页数 + 堆页数)× random_page_cost + CPU 代价
≈ (1 + 1) × 4.0 = 8.0 ← ts_hdd
≈ (1 + 1) × 1.1 = 2.2 ← ts_ssd代价差距本身不会直接加速查询,它的意义在于:当索引扫描的代价与顺序扫描的代价接近时,这个差距足以让规划器翻转选择。表越大、随机读页越多,差距越显著。(上例的代价数值随统计信息与表大小浮动,实际应以自己集群的 EXPLAIN 输出为准。)
用 EXPLAIN 检查表的实际位置
如果怀疑表没落在预期表空间上,可以直接查目录:
SELECT c.relname, t.spcname
FROM pg_class c
LEFT JOIN pg_tablespace t ON t.oid = c.reltablespace
WHERE c.relname IN ('t_ssd', 't_hdd');reltablespace = 0 表示该对象使用数据库的默认表空间。
场景三:分区表冷热分层的完整落地
这是表空间级参数最典型的生产用法。假设 orders 按月分区,最近 3 个月是热数据。
-- 1. 父表默认落在热点表空间(未来新分区自动继承)
CREATE TABLE orders (
order_id bigint NOT NULL,
user_id bigint NOT NULL,
created_at timestamptz NOT NULL,
amount numeric(12,2)
) PARTITION BY RANGE (created_at)
TABLESPACE ts_ssd;-- 2. 新分区显式指定热点表空间
CREATE TABLE orders_2026_09 PARTITION OF orders
FOR VALUES FROM ('2026-09-01') TO ('2026-10-01')
TABLESPACE ts_ssd;-- 3. 用一条 SQL 生成历史分区的迁移语句,避免手写漏掉
SELECT format('ALTER TABLE %I SET TABLESPACE ts_hdd;', c.relname) AS migrate_sql
FROM pg_inherits i
JOIN pg_class c ON c.oid = i.inhrelid
WHERE i.inhparent = 'orders'::regclass
AND c.relname < 'orders_2026_07'
ORDER BY c.relname; migrate_sql
--------------------------------------------
ALTER TABLE orders_2025_01 SET TABLESPACE ts_hdd;
ALTER TABLE orders_2025_02 SET TABLESPACE ts_hdd;
ALTER TABLE orders_2025_03 SET TABLESPACE ts_hdd;
...-- 4. 索引必须单独迁移,不要以为跟着表走
SELECT format('ALTER INDEX %I SET TABLESPACE ts_hdd;', c.relname) AS migrate_sql
FROM pg_inherits i
JOIN pg_class c ON c.oid = i.inhrelid
JOIN pg_index x ON x.indrelid = c.oid
JOIN pg_class c2 ON c2.oid = x.indexrelid
WHERE i.inhparent = 'orders'::regclass
AND c.relname < 'orders_2026_07'
AND c2.relname LIKE '%_idx';SET TABLESPACE 是表重写,会产生等量 WAL
ALTER TABLE ... SET TABLESPACE 本质上把整表重写一遍:持有 ACCESS EXCLUSIVE 锁、写等量的 WAL、耗时与表大小成正比。历史分区通常很大,务必安排在低峰期,并提前确认磁盘余量与 WAL 目录容量。
场景四:把临时文件与维护 I/O 隔离
除了数据,临时文件(排序、哈希溢出)也是表空间的适用对象,而且它们往往被忽略。
-- 让临时文件只落在独立的表空间,避免挤占数据盘的 I/O
ALTER SYSTEM SET temp_tablespaces = 'ts_tmp';-- 单会话临时覆盖(只在当前会话生效,刷新后恢复)
SET temp_tablespaces = 'ts_tmp';同时,如果某个表空间是 NVMe,可以单独提升它的维护 I/O 并发:
-- 让该表空间上的 VACUUM / CREATE INDEX 更充分地利用并发 I/O
ALTER TABLESPACE ts_ssd SET (maintenance_io_concurrency = 64);场景五:评估一次调优是否真的有效
参数调优必须可验证。推荐用「先测基线、再改参数、再测」的流程,并且不要只看单次耗时。
-- 基线:记录当前表空间参数
SELECT spcname, spcoptions FROM pg_tablespace WHERE spcname = 'ts_ssd';-- 采集慢查询的执行计划(改参数前后各执行一次,保存输出对比)
EXPLAIN (ANALYZE, BUFFERS)
SELECT user_id, count(*)
FROM orders
WHERE created_at >= now() - interval '7 days'
GROUP BY user_id;对照时重点看两处:
- 执行计划是否发生了预期的变化(比如顺序扫描换成索引扫描,或反之);
BUFFERS中的read是否下降——read表示从磁盘读取的块数,代价参数不该改变它,改变的应是计划选择。
-- 示意输出(关键字段)
GroupAggregate (cost=... rows=... width=...)
Buffers: shared hit=1520 read=840不要根据几次实验就改代价参数
官方在 Planner Cost Constants 一节开头明确警告:Unfortunately, there is no well-defined method for determining ideal values for the cost variables. They are best treated as averages over the entire mix of queries that a particular installation will receive. This means that changing them on the basis of just a few experiments is very risky.
代价参数应被视为整个负载的平均值,只针对少数查询调参风险很高。
注意事项
不要以为建了表空间查询就会变快。 收益来自物理盘的速度差异或 I/O 隔离,表空间本身不缓存、不压缩、不加索引。
不要只为「每个盘建一个表空间」而建。 官方提醒:在同一个逻辑文件系统上建多个表空间通常没有意义。表空间应该按「快慢差异明显」来划分。
不要在 PG 14 及更早版本上用表空间级参数。
CREATE TABLESPACE ... WITH (...)会直接报语法错误。sql-- 错误!PostgreSQL 14 不支持 CREATE TABLESPACE ts_ssd LOCATION '/data/ssd/pg' WITH (random_page_cost = 1.1);不要试图在表空间上设置四个参数之外的任何参数。
tablespace_option是封闭集合。sql-- 错误!effective_cache_size 不是合法的 tablespace_option ALTER TABLESPACE ts_ssd SET (effective_cache_size = '32GB');不要把
random_page_cost设成小于seq_page_cost。 官方明确说这在物理上不合理;只有在数据库完全常驻内存时才考虑让两者相等。不要以为调小
random_page_cost就一定更快。 它只改变规划器的估算。如果估算偏离真实缓存命中率,可能反而选出更差的计划。不要指望代价参数影响写路径。 PG 18 的 AIO 仍是只读特性,
io_method不改变写入行为。不要忘记索引也要迁移。
ALTER TABLE ... SET TABLESPACE不会带上索引,分区表上更是几十条,必须逐条处理。不要在业务高峰期做冷热迁移。
SET TABLESPACE是重写,会产生等量 WAL 并持有ACCESS EXCLUSIVE锁。不要把调参当作性能问题的万能药。 如果瓶颈是 CPU、锁或缺失索引,代价参数无法解决。
最佳实践
只在「盘的速度差异明显」时使用表空间级参数。 全 SSD 集群通常不需要分层,只需考虑全局
random_page_cost是否偏高。sql-- 全闪存集群:全局压低一次即可,无需按表空间区分 ALTER SYSTEM SET random_page_cost = 1.1;SSD/NVMe 表空间上显式设置
random_page_cost与effective_io_concurrency,让规划器知道这块盘的真实速度。sqlALTER TABLESPACE ts_ssd SET (random_page_cost = 1.1, effective_io_concurrency = 200);机械盘表空间保持默认代价即可,不要为了「统一」而把 HDD 也改成
1.1。4.0对机械盘是合适的。用一条 SQL 生成迁移 DDL,不要手写。 分区与索引数量多,手写必漏。
sqlSELECT format('ALTER TABLE %I SET TABLESPACE ts_hdd;', c.relname) FROM pg_inherits i JOIN pg_class c ON c.oid = i.inhrelid WHERE i.inhparent = 'orders'::regclass AND c.relname < 'orders_2026_07';把
spcoptions纳入配置巡检,和表空间清单一起监控。 参数被误改或漏设,都会静默影响执行计划。sqlSELECT spcname, spcoptions, pg_size_pretty(pg_tablespace_size(oid)) AS size FROM pg_tablespace ORDER BY pg_tablespace_size(oid) DESC;调参前先量化瓶颈。 用
pg_stat_statements找出真正的高耗时/高 I/O 查询,再决定是否值得调代价参数。sqlSELECT calls, mean_exec_time, shared_blks_read, query FROM pg_stat_statements ORDER BY shared_blks_read DESC LIMIT 10;调参要留痕、可回滚。 记录改动前后的
spcoptions与对应执行计划,出问题时能快速RESET。sql-- 回滚到全局默认 ALTER TABLESPACE ts_ssd RESET (random_page_cost, effective_io_concurrency);在 PG 18 上可评估
io_method = io_uring的收益,但要先确认编译时带了--with-liburing,并在测试环境验证稳定性。sql-- 查看当前方法;确认内核与编译参数后再切换 SHOW io_method;
下一节
表空间解决了「数据放在哪块盘」的问题。接下来进入事务与并发控制,从最基础的事务控制语句开始:BEGIN / COMMIT / ROLLBACK。
参考链接
- PostgreSQL 18 官方文档:ALTER TABLESPACE(
tablespace_option与SET/RESET) - PostgreSQL 18 官方文档:CREATE TABLESPACE
- PostgreSQL 18 官方文档:Planner Cost Constants(
seq_page_cost/random_page_cost/effective_cache_size) - PostgreSQL 18 官方文档:Resource Consumption — I/O(
effective_io_concurrency/maintenance_io_concurrency/io_method) - PostgreSQL 18 官方文档:Release Notes(AIO 子系统与默认值变更)
- PostgreSQL 18 官方文档:Tablespaces(管理与使用)
- PostgreSQL 18 官方文档:ALTER TABLE(
SET TABLESPACE与ALL IN TABLESPACE) - PostgreSQL 18 官方文档:Partition Pruning(分区裁剪)
- PostgreSQL 18 官方文档:pg_stat_statements