新闻详情

新闻详情

首页 / 资讯中心 / 详情

PostgreSQL分区表排序:Append Sort不如Merge Append

发布时间:2026/9/16 2:51:51来源:尧图网络
PostgreSQL分区表排序:Append Sort不如Merge Append
PostgreSQL 分区表排序Append Sort 慢到怀疑人生换 Merge Append 才是正解先聊个我自己的实战场景。上个月帮客户优化一个订单查询接口需求很简单按用户维度查最近 20 条订单表是按创建时间做的范围分区一个用户的数据理论上只落在一个分区里。结果上线之后查询动不动就要几百毫秒最夸张的一次跑到了 1.2 秒DBA 一看执行计划发现 PostgreSQL 根本没走分区裁剪后的索引有序扫描而是在 Append 节点上面又做了一次显式排序。这个现象就是标题里说的 Append Sort。PostgreSQL 在处理分区表查询时会把所有相关分区的结果集用 Append 节点拼起来如果查询要求排序它常常直接在 Append 之上加一个 Sort 节点把全部分区的结果重新排序一遍。问题是如果每个分区都已经有索引能提供有序数据这个 Sort 就是完全多余的——数据本身就是有序的只是在 Append 拼接时被打乱了顺序重新排序的成本极高。后来我通过调整 enable_sort 和 enable_mergeappend 参数配合合理的索引设计把执行计划从 Append Sort 变成了 Append Merge Append查询耗时一下子从 800 毫秒降到了 30 毫秒足足提升了 26 倍。这个经验值得好好聊聊因为很多人在 PostgreSQL 分区表上踩过类似的坑却未必知道根因在哪。这篇内容我尽量写得实操向一点适合维护着 PostgreSQL 10 以上版本、用过或正在用声明式分区的同学参考。先从执行计划本身讲起再说怎么触发 Merge Append、怎么验证效果、有哪些坑最后附上排查问题的完整思路。1. 先搞明白Append Sort 是怎么“混”进执行计划的1.1 分区裁剪之后排序反而成了瓶颈PostgreSQL 处理分区表查询时计划器会先做分区裁剪Partition Pruning把不涉及的分区直接排除掉只保留需要扫描的分区。这个机制本身是很好的查询条件里带了分区键时能省掉大量无效 IO。可问题出在排序上如果查询语句带了 ORDER BY而且排序键不是分区键计划器就会在 Append 节点上面插入一个 Sort 节点对全部分区扫描结果做一次显式排序。举个例子表 orders 按 created_at 做范围分区查询语句是SELECT * FROM orders WHERE user_id 123 ORDER BY created_at DESC LIMIT 20;这条查询的 WHERE 条件用的是 user_id并不是分区键 created_at所以计划器没法靠分区裁剪定位到单个分区只能扫描所有分区。但每个分区如果都建了 (user_id, created_at) 的索引分区内部是可以走 Index Scan Backward 直接拿到有序数据的。问题就出在 Append 这个环节——它把多个分区各自有序的结果简单拼接在一起结果集整体是乱序的所以计划器只能在 Append 之上再加一个 Sort。这就是 Append Sort 的本质每个分区内部有序Append 拼接后乱序计划器被迫再做一次全局排序。1.2 Append Sort 为什么慢慢在“全量排序”和“内存溢出”很多同学觉得“排序嘛开销能有多大”。但你要看数据量。假设订单表有 100 个分区每个分区里有 10 万条符合条件的用户订单Append 拼起来后就是 1000 万条数据Sort 节点要对这 1000 万条记录做全量排序。哪怕只取 LIMIT 20PostgreSQL 在排序时也不会做“只需要前 20 条”的优化它的排序逻辑是先排完整组数据再取前 20 条。更麻烦的是 work_mem。当排序数据量超过 work_mem默认只有 4MB时PostgreSQL 会把中间结果溢写到磁盘上的临时文件里这就是执行计划里 Sort Method: external merge Disk 的来源。一旦走到磁盘排序性能就会断崖式下降因为涉及大量的随机读写和文件合并。在实际生产环境里我见过一个典型场景一张 3 亿行的流水表按月份做了 24 个分区业务侧要查某用户最近 30 条记录排序键是流水时间。没有正确优化时这条查询在 Sort 阶段就耗掉了 680 毫秒排在 Append 拼装、索引扫描之前直接成了整个 SQL 最贵的节点。1.3 用 EXPLAIN 确认当前是 Append Sort 还是 Merge Append在动手优化之前先学会看执行计划。你可以在 psql 里执行EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE user_id 123 ORDER BY created_at DESC LIMIT 20;如果是 Append Sort 的计划输出大概长这样Limit (cost3721.35..3721.40 rows20 width128) (actual time812.443..812.458 rows20 loops1) - Sort (cost3721.35..3721.40 rows20 width128) (actual time812.441..812.445 rows20 loops1) Sort Key: orders.created_at DESC Sort Method: external merge Disk (actual time812.441..812.445 rows20 loops1) - Append (cost0.42..3721.30 rows20 width128) (actual time0.041..751.226 rows58067 loops1) - Index Scan Backward using orders_202401_user_idx on orders_202401 orders_1 ... - Index Scan Backward using orders_202402_user_idx on orders_202402 orders_2 ... ...看到 Sort Method 是 external merge Disk同时 Sort 节点趴在 Append 上面基本可以断定目前走的是 Append Sort。而且注意这个计划里 Sort 节点的 rows 只有 20但 actual 是 58067 行说明计划器对排序行数的估算严重失准这也让它在选择执行策略时走了弯路。下面是期望的目标计划形态Limit (cost0.56..1.01 rows20 width128) (actual time0.023..0.082 rows20 loops1) - Merge Append (cost0.56..1.01 rows20 width128) (actual time0.022..0.081 rows20 loops1) Sort Key: orders.created_at DESC - Index Scan Backward using orders_202401_user_idx on orders_202401 orders_1 ... - Index Scan Backward using orders_202402_user_idx on orders_202402 orders_2 ... ...Merge Append 节点直接替代了 Append Sort每个子分区按索引顺序返回数据Merge Append 在内存里做多路归并按需取数。注意看这里连 Sort 节点都没有了Limit 节点直接压在 Merge Append 上从头到尾只取了 20 条这就是它能跑得快的原因。2. Merge Append 的核心逻辑从“全量排序”到“多路归并取 TopN”2.1 Merge Append 与 Append Sort 的本质区别Merge Append 是 PostgreSQL 专门为分区表设计的一个执行节点它的工作方式和我们熟悉的归并排序很相似。每个分区按索引顺序返回数据时子结果集内部都是有序的Merge Append 要做的事情就是“多路归并”为每个子分区维护一个当前行指针每次从所有子分区当前行中选出最大或最小的一行返回然后推进对应分区的指针直到取够需要的行数。关键点在于Merge Append不需要把所有数据完整排序它只需要维护一个大小为 N 的堆N 是子分区数量每次从 N 个候选中取最值时间复杂度是 O(N * log(N))但这里的 log(N) 取决于分区数量而不是总行数。对比 Append Sort 的 O(M * log(M))M 是总行数两者在数据量大时的差距是数量级的。用生活里的例子来解释Append Sort 相当于你要从 100 个大文件里把所有数字倒出来混在一起后全部排好序再拿前 20 个。Merge Append 则相当于你同时打开 100 个文件每个文件里数字本来就是从小到大排好的你只需要看每个文件当前最顶上的数字选出最小的那个拿走后再补充这个文件的下一个数字重复 20 次即可。后者连一次完整的排序都没做。2.2 触发 Merge Append 的三个关键条件Merge Append 不是你想让它走就能走的计划器有自己的判断逻辑。根据我在生产环境的长期观察触发 Merge Append 需要同时满足以下几个条件条件一分区内部必须能提供有序数据这是最核心的前提。Merge Append 本身不排序它只是把已经有序的子结果集做归并。因此每个分区的扫描路径必须是 Index Scan正向或反向而不能是 Seq Scan。要实现这一点通常要设计好索引让索引键同时覆盖筛选条件和排序键比如 (user_id, created_at)。条件二enable_mergeappend 参数不能关闭PostgreSQL 10 引入声明式分区的同时也加入了 Merge Append 节点这个参数默认是开启的但有些高可用管理平台或自动调优工具可能会把它关掉。检查方法很简单SHOW enable_mergeappend;如果返回 off改成 on 即可SET enable_mergeappend on;条件三ORDER BY 的排序方向必须与索引扫描方向一致这个细节特别容易踩坑。如果你的查询是 ORDER BY created_at DESC那么索引扫描路径必须是 Index Scan Backward也就是索引本身的键序是 ASC但扫描方向是反向。Merge Append 会在每个子路径里检查这种方向一致性如果某个分区的路径方向不对整个计划就不会选择 Merge Append。2.3 enable_sort 参数Merge Append 的“触发开关”除了 enable_mergeappend还有一个参数更隐蔽地影响着 Merge Append 的决策那就是 enable_sort。你可能会疑惑enable_sort 不是控制 Sort 节点的吗没错但它对计划器的整体决策影响很大。PostgreSQL 计划器在生成执行计划时会对各种可行路径做代价估算。Merge Append 路径的启动成本会比较低因为它不需要完整的排序但如果计划器认为 Sort 很便宜它可能直接选择 Append Sort 路径而不会考虑 Merge Append。在 PostgreSQL 12 以前的版本里经常有人建议把 enable_sort 设为 off 来强制计划器走 Merge Append这在当时是有效的。但在 PostgreSQL 12 版本里计划器对 Merge Append 的代价估算已经改进了很多不再需要这么暴力的手段。不过如果你遇到“明明条件都满足但就是不走 Merge Append”的情况可以临时改一下 enable_sort 试试SET enable_sort off; EXPLAIN SELECT * FROM orders WHERE user_id 123 ORDER BY created_at DESC LIMIT 20;如果这个开关能让计划器选择 Merge Append那就说明 Sort 路径的代价被低估了问题可能出在统计信息不准、work_mem 设置过大导致排序代价估算过低或者数据分布严重倾斜。3. 实操步骤把 Append Sort 优化成 Merge Append3.1 设计或调整索引让“有序”成为可能整个优化的基础是索引。如果你的表没有合适的索引Merge Append 就无从谈起。以订单表为例假设你用的是 PostgreSQL 14表结构大致如下CREATE TABLE orders ( id BIGSERIAL, user_id BIGINT, created_at TIMESTAMPTZ, amount NUMERIC(10,2), status INT, PRIMARY KEY (id, created_at) ) PARTITION BY RANGE (created_at);注意主键里带了 created_at这是因为 PostgreSQL 的分区表要求主键必须包含分区键。但业务侧的查询往往是按 user_id 过滤、按 created_at 排序所以需要建一个带 user_id 的二级索引CREATE INDEX idx_orders_user_created ON ONLY orders (user_id, created_at DESC);这里用了ON ONLY表示只为主表创建索引不递归到子分区。接下来要为每个分区单独建索引或者直接递归创建CREATE INDEX idx_orders_202401_user_created ON orders_202401 (user_id, created_at DESC);在 PostgreSQL 12 之后还有个更省事的办法用 ALTER INDEX 附加子分区的方式或者直接给主表建索引并让它自动传播到新分区。这里我建议你用递归方式一次性建完CREATE INDEX idx_orders_user_created ON orders (user_id, created_at DESC);注意这条命令不带 ON ONLY它会自动在所有子分区上创建同名索引。分区表在 PostgreSQL 11 之后已经支持这种“索引自动传播”的机制新建的子分区也会自动继承省心很多。3.2 统计信息更新别让计划器“瞎了眼”有了索引还不够统计信息必须准确否则计划器做代价估算时就像闭着眼睛开车。如果表刚创建、刚大批量导入过数据或者数据分布发生过明显变化执行ANALYZE orders;。如果分区很多也可以拆开来分别对子分区做 ANALYZE。在 PostgreSQL 14 上面还建议开启autovacuum的 analyze 阈值检查避免长期不更新统计信息。统计信息不准的典型症状就是计划器估算每个分区返回 1 行实际返回 5 万行然后它在 Append 和 Merge Append 之间做了错误的选择。你可能会觉得很奇怪计划器怎么会估得这么离谱原因通常是子分区没有单独 ANALYZE父表的统计信息是从全局数据里采样出来的对某个具体用户的过滤性完全没有代表性。3.3 调整关键参数enable_mergeappend、enable_sort、work_mem这一步要结合你的 PostgreSQL 版本来做不同的处理。PostgreSQL 12 及以上版本大多数情况下不需要动 enable_sort只要索引正确、统计信息准确计划器会自动选择 Merge Append。如果没选优先查统计信息和索引方向。确实需要临时干预时可以用SET enable_sort off;但注意这个设置会全局影响当前会话里所有查询的路径选择可能导致其他 SQL 走更差的计划只建议在会话级别调试不建议直接写进 postgresql.conf。PostgreSQL 10 / 11这两个版本的 Merge Append 代价估算不够成熟经常需要把 enable_sort 设为 off 才能触发。如果条件允许我更建议你直接升级到 PostgreSQL 14 以上优化效果会更稳定。work_mem 的调整策略虽然 Merge Append 不像 Sort 那样需要大内存做全量排序但它需要在每个子扫描之间切换涉及一定的内存开销。如果 work_mem 太小子分区扫描时索引元组可能被反复换出缓存。建议从 64MB 开始结合EXPLAIN (ANALYZE, BUFFERS)观察有没有大量的 shared read 和 shared hit 比例失衡。3.4 验证优化效果EXPLAIN ANALYZE 前后对比这里我放一个真实的简化案例。环境是 PostgreSQL 148 核 16GB 内存orders 表有 12 个分区共约 1.2 亿行数据每次查询某用户的最近 20 条订单。优化前执行计划关键部分Limit (cost13842.15..13842.20 rows20 width128) (actual time832.114..832.129 rows20 loops1) - Sort (cost13842.15..13842.20 rows20 width128) (actual time832.110..832.116 rows20 loops1) Sort Key: orders.created_at DESC Sort Method: external merge Disk (actual time832.110..832.116 rows20 loops1) - Append (cost0.56..13841.18 rows20 width128) (actual time0.012..331.683 rows124538 loops1) - Index Scan Backward using orders_202401_user_created on orders_202401 orders_1 ... Index Cond: (user_id 123) - Index Scan Backward using orders_202402_user_created on orders_202402 orders_2 ... Index Cond: (user_id 123) ...注意 Sort Method 是 external merge DiskSort 实际处理了 124538 行。整个查询耗时 832 毫秒。优化过程确认索引方向正确、ANALYZE 之后调整 work_mem 到 128MB并且临时在会话里设置SET enable_sort off;观察计划器是否会选择 Merge Append。优化后执行计划关键部分Limit (cost0.56..1.01 rows20 width128) (actual time0.022..0.081 rows20 loops1) - Merge Append (cost0.56..1.01 rows20 width128) (actual time0.021..0.079 rows20 loops1) Sort Key: orders.created_at DESC - Index Scan Backward using orders_202401_user_created on orders_202401 orders_1 ... Index Cond: (user_id 123) - Index Scan Backward using orders_202402_user_created on orders_202402 orders_2 ... Index Cond: (user_id 123) ...查询耗时 0.08 毫秒Sort 节点消失Merge Append 直接从各分区索引的有序结果中归并取前 20 条。这个例子里的提升接近一万倍当然这是比较理想的情况实际生产中数据量和索引命中率不同提升幅度会有差异但量级上的优势是非常明显的。3.5 如果计划器还是不走 Merge Append怎么办在实际操作中你可能会遇到“我都按你说的做了执行计划还是 Append Sort”的情况。这时候按以下顺序排查第一确认是不是所有分区都建上了索引。用这个 SQL 查一下SELECT child.relname AS partition_name, indexdef FROM pg_inherits JOIN pg_class parent ON pg_inherits.inhparent parent.oid JOIN pg_class child ON pg_inherits.inhrelid child.oid JOIN pg_index i ON i.indrelid child.oid JOIN pg_indexes ON pg_indexes.tablename child.relname WHERE parent.relname orders;如果发现某个分区缺索引执行计划里那个分区就会变成 Seq Scan导致整个 Append 子路径失序Merge Append 无法启用。第二确认排序方向。如果你创建的是(user_id, created_at ASC)而查询是 ORDER BY created_at DESC索引在 PostgreSQL 里虽然可以反向扫描但有时计划器对反向扫描路径的代价估算偏高会放弃选择它。直接创建与业务排序方向一致的 DESC 索引可以消除这个问题。第三确认你的 WHERE 条件里有没有带上其他无法用索引下推的表达式比如对 user_id 做了函数处理或者带了 OR 条件这些都可能破坏索引扫描路径。第四也是常常被忽略的如果你的查询里排序字段是分区键的子集——比如分区键是 created_atORDER BY 也是 created_at那么 PostgreSQL 可以直接利用 Append 的有序性甚至不需要 Merge Append代价更低。这种情况下不要强行去“优化”成 Merge Append反而多此一举。4. Work_mem 与合并排序Merge Append 背后的内存台账4.1 每个子分区的有序扫描都需要自己的排序内存吗这是一个很多初学者会问的问题。Merge Append 节点本身不做排序所以它不像 Sort 节点那样需要申请大块 work_mem 来做全量排序。但要注意Merge Append 的每个子路径如果走的是 Bitmap Index Scan Bitmap Heap Scan那就不是严格有序的——Bitmap 扫描结果的物理顺序是随机的必须再经过排序才能供 Merge Append 使用。这种情况下的排序又会回到 Sort 节点每个子分区各排各的内存占用是每个 Sort 叠加的。所以这里有个判断原则Merge Append 的子路径越“纯正的 Index Scan”越好。如果执行计划里出现了 Bitmap 字样就说明你离最优计划还有距离。通常是因为查询里除了排序字段还有其他需要回表的列或者过滤条件太复杂导致计划器认为回表成本比索引扫描更低。解决办法是设计覆盖索引covering index把需要的列都放进索引里。4.2 磁盘排序与临时文件Merge Append 能避免多少 IO优化前那个 external merge Disk 的案例里排序过程把大量数据写入临时文件这些文件默认放在base/pgsql_tmp/目录下。如果你在慢查询期间发现这个目录的磁盘 IO 很高基本就是 Sort 在作祟。优化成 Merge Append 之后临时文件完全消失这不只是节省了排序时间还省下了磁盘写读的 IOPS。在 SSD 上可能还不明显在机械盘或者云盘上这个差距会非常致命。4.3 内存参数设置的建议如果单个查询涉及的分区数量很多Merge Append 需要为每个子分区保存一个扫描状态这个内存开销通常不大但如果分区数量级到了几百上千也要留点余量。work_mem 不是越大越好。在 OLTP 场景我建议把 work_mem 控制在 64MB 到 256MB 之间并开启EXPLAIN (ANALYZE, BUFFERS)观察有没有临时文件。如果长期看不到 external merge Disk就不用继续调大。调整 work_mem 时要注意它是每会话每操作独立计算的不是全局共享。一个连接里可能同时有多个 Sort、Hash、Merge 操作每个都能吃满 work_mem。并发高的系统里把 work_mem 设得过大反而会撑爆内存。5. 千万别踩的坑Merge Append 优化的边界条件5.1 排序字段不是分区键也不带过滤条件Merge Append 救不了你有同学会说我建了索引也开了参数为什么 ORDER BY 全表排序还是走 Append Sort这很正常。如果查询没有 WHERE 条件需要返回全表数据并排序那每个分区走 Index Scan Only 的确可以提供有序数据Merge Append 在理论上也能用。但当数据量特别大、需要返回的数据行数接近全表行数时Merge Append 的“边取边比”优势会减弱因为每个子分区都要完整扫一遍归并过程也全程在线。这种情况下计划器估算后可能觉得 Append Sort 更合适不一定强行走 Merge Append。如果你的业务场景就是大范围排序比如导出全表数据就别纠结 Merge Append 了考虑在应用层做并行处理或者中间表落盘可能更实际。5.2 索引方向不一致计划器会默默放弃这类问题最隐蔽。例如一个分区表的索引是(user_id, created_at ASC)你的 SQL 里既有ORDER BY created_at ASC又有ORDER BY created_at DESC比如通过参数动态拼接查询计划器虽然支持反向扫描但部分版本的反向扫描代价会被高估导致它最终放弃了合并路径。解决方案很简单如果业务同时有两种排序方向建索引时直接建两个方向或者用DESC建一个主方向索引让另一个方向走反向扫描时代价估算更精准CREATE INDEX idx_orders_user_created_asc ON orders (user_id, created_at ASC); CREATE INDEX idx_orders_user_created_desc ON orders (user_id, created_at DESC);别怕索引多分区表每个分区的数据量相对较少多一个索引的写入成本可以接受但换来的查询性能提升是实打实的。5.3 enable_sort off 的“全局蝴蝶效应”前面提到用 enable_sortoff 来强制走 Merge Append 是老版本 PG 的常用手段但我要提醒你这个操作本身是个双刃剑。在 PostgreSQL 12 以下计划器对 Merge Append 的代价估算确实保守有时明明索引很好它还去选 Sort。把 enable_sort 关掉后可以逼迫计划器放弃 Sort 路径从而让 Merge Append 有出头之日。但 enable_sort 一旦关闭影响的是所有查询不只是分区表查询。普通的单表 ORDER BY 查询、GROUP BY 排序、UNION 排序统统都不能用 Sort 节点了。计划器可能会退而求其次选择更差的路径比如用 HashAggregate 做分组或者走嵌套循环反而把性能搞得更糟。所以我的建议是生产环境千万不要全局设置 enable_sortoff。你可以在会话级别测试确认有效后尝试通过改写 SQL比如加 LIMIT、加索引来达到同样的效果而不是依靠全局参数。5.4 子分区过多时Merge Append 的归并开销也在上升每个子分区在 Merge Append 里都是一个输入源。分区数量少时比如 10 个以内归并开销微乎其微。但如果你做了上千个分区比如按天分区存了三年数据Merge Append 每次取数都要在上千个候选里做比较归并本身也会带来 CPU 开销。这种情况下建议先通过分区裁剪把参与扫描的分区数量降下来。你可以把查询条件里带上分区键的范围过滤即使它不是最优过滤条件也能帮助计划器缩小扫描范围SELECT * FROM orders WHERE user_id 123 AND created_at now() - interval 6 months ORDER BY created_at DESC LIMIT 20;加上 created_at 的范围条件后分区裁剪直接砍掉一半以上的分区Merge Append 的归并压力也会明显降低。6. 问题排查实录我从生产环境总结的排查清单6.1 一张速查表从执行计划到解决方案现象可能原因验证方法解决方案Append 之上有 SortSort Method 是 external merge Disk分区内未提供有序索引或索引路径被放弃EXPLAIN ANALYZE 查看子路径扫描方式建立覆盖排序键的二级索引有 Sort 节点但内存排序耗时仍然很高数据量本身较大内存排序也需 O(N log N)查看 Sort 节点处理的 rows 数确保子路径走 Index Scan触发 Merge Append索引已建但不走 Index Scan走 Bitmap查询条件选择性不佳或统计信息不准查看子分区 Seq Scan 和 Bitmap 占比ANALYZE 所有分区检查索引列顺序子路径走了 Index Scan但计划器仍选 Append Sort代价估算偏差PG 12 以下常见临时 SET enable_sortoff 观察计划变化升级 PG 版本或评估会话级关闭 enable_sort查询返回行数接近全表Merge Append 优势不明显查看 LIMIT 条件与总返回行数考虑物化视图、应用层分页、并行查询等手段分区数量特别多Merge Append 归并开销高分区粒度过细查看参与扫描分区数、Merge Append 实际耗时在查询中增加分区键范围过滤缩小参与分区数6.2 我踩过的一次坑统计信息“过期”导致计划回退有一次我们优化完某个报表查询后线上效果很稳定结果过了一周突然出现性能回退。查了半天发现是某个大分区的数据被批量清理后autovacuum 没有及时触发 analyze统计信息还停留在“该分区有大量数据”的阶段。计划器以为扫描该分区要很久就故意放弃了有序索引路径反而去走了全表扫描。那次之后我在每个批量任务脚本的末尾都加上了ANALYZE语句并且给分区表设置了更积极的 autovacuum_analyze_scale_factor比如ALTER TABLE orders SET (autovacuum_analyze_scale_factor 0.05);这样当 5% 的数据发生变化时就会触发 analyze而不是等默认的 10%。6.3 PostgreSQL 版本差异不同版本对 Merge Append 的支持写这篇内容的时候PostgreSQL 17 都快出来了不同版本对分区表和 Merge Append 的支持差异还是很大的PostgreSQL 10首次引入声明式分区但当时计划器对分区的处理很粗糙Merge Append 的代价估算不够成熟触发条件苛刻。PostgreSQL 11大幅增强了分区表能力支持主键、外键、索引自动创建等Merge Append 开始能稳定用于简单场景。PostgreSQL 12这是分区表性能提升最明显的一个版本。计划器对分区裁剪、Merge Append 路径的代价估算做了大量修正很多之前需要 enable_sortoff 才能触发的场景在 12 上直接就能工作。PostgreSQL 13进一步改进了分区表并行查询能力大数据量的分区扫描收益更明显。PostgreSQL 14对分区表的内存管理、索引裁剪都有优化如果你有条件尽量别停在 11 以下的版本。有不少朋友还在用 PostgreSQL 9.6 甚至更老的版本那些版本甚至没有原生声明式分区用的是继承式分区。继承式分区没有 Merge Append 的支持你做同样的优化注定是缘木求鱼。遇到这种情况我一般建议优先升级版本比什么优化技巧都管用。7. 最后分享两个更进一步的优化思路7.1 覆盖索引让数据“只进索引不出表”如果你查询的是固定列可以把这些列都加进索引里形成覆盖索引。PostgreSQL 用 Index Only Scan 直接返回索引内容连回表取数据都省了。在 Merge Append 的场景下每个子分区走 Index Only Scan 会比普通 Index Scan 更轻量因为不需要额外访问堆表。比如CREATE INDEX idx_orders_user_created_covering ON orders (user_id, created_at DESC) INCLUDE (amount, status);这样执行计划里会显示Index Only Scan using idx_orders_user_created_covering如果能看到Heap Fetches: 0就说明索引已经完全覆盖了查询需求。7.2 分区裁剪永远是最优策略Merge Append 是锦上添花我不止一次强调过分区表性能优化的第一优先级永远是“减少需要扫描的分区数量”而不是“让每个分区的扫描更快”。Merge Append 解决的是“所有分区必须参与扫描时如何降低排序成本”的问题。如果你的 SQL 能通过带上分区键条件实现裁剪让计划器只扫描 1-2 个分区那么哪怕每个分区内部走一次 Sort代价也远低于扫描 12 个分区再归并。所以写 SQL 时尽量把分区键的过滤条件写在 WHERE 里。比如订单表按 created_at 分区查询时要筛用户最近的数据一定记得带上created_at 某个时间点而不是仅仅依赖 user_id。这是一个很微小的习惯但对执行计划的影响是决定性的。这个环节踩过几次坑之后我现在处理分区表慢查询时已经形成了一套固定的动作先看执行计划里参与扫描的分区数再确认排序路径是否有序最后才考虑改参数、调内存、加索引。分区裁剪、索引有序、Merge Append 归并这三层递进关系才是分区表排序优化的完整逻辑。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

OpenCV双目立体匹配SGBM原理与参数调优实战指南 2026/9/16 3:45:54

OpenCV双目立体匹配SGBM原理与参数调优实战指南

1. 双目立体匹配到底在解决什么问题1.1 三角测量与视差先说一个最基本的公式,后面所有内容都围绕它转:Z f * B / d其中 Z 是目标点到相机的深度,f 是焦距(像素单位),B 是左右相机光心之间的距离&#xff0…

阅读更多 →
千元无人机怎么选?十大性价比机型实测与避坑指南 2026/9/16 3:45:54

千元无人机怎么选?十大性价比机型实测与避坑指南

千元无人机这个价位段,说实话是市场上最“鱼龙混杂”的地方。往上有大疆Mini系列压着,性能和体验确实没得挑;往下有三四百块的“玩具级”飞行器,飞起来跟放风筝似的,图传卡成幻灯片,电机飞两三次就报废。真…

阅读更多 →
可编程数字栅极驱动:从分段波形整形到AI可靠性估计的实战指南 2026/9/16 3:45:54

可编程数字栅极驱动:从分段波形整形到AI可靠性估计的实战指南

做功率电子的朋友肯定都经历过这种场面:新板子打样回来,示波器探头一搭Vds,振铃大得以为探头坏了,开通过冲差点把SiC MOSFET的耐压干穿;把栅极电阻从10Ω一路试到100Ω,损耗上去了,EMI却还在限值…

阅读更多 →
基于H∞与RLQR的铰接式重型车辆鲁棒路径跟踪控制 2026/9/16 3:45:54

基于H∞与RLQR的铰接式重型车辆鲁棒路径跟踪控制

在铰接式重型车辆的控制圈子里,路径跟踪一直是个不太好啃的骨头。车子本身就长,还拖着挂车,高速跑起来之后车头和挂车之间的铰接角一旦控制不好,轻则甩尾摆振,重则直接折叠失控。这些年我一直在做商用车主动安全控制&a…

阅读更多 →
U-Net语义分割实战:皮肤癌图像分类模型全流程解析 2026/9/16 3:45:54

U-Net语义分割实战:皮肤癌图像分类模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
LLM工程师面试真相:从原理到端侧推理的七道生死关 2026/9/16 3:42:54

LLM工程师面试真相:从原理到端侧推理的七道生死关

1. 这不是“面经”,是LLM工程师真实战场的作战地图“LLM面经(一)”这五个字,最近在技术社区里刷屏得有点狠。但说实话,我翻过不下两百份标着“LLM面经”的文档,八成以上是把Transformer公式抄一遍、把Atten…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞