第3章:分区裁剪原理
分区裁剪,说白了就是数据库在执行查询时,只扫描必要的分区,跳过那些无关的分区。我刚开始接触这个特性时,觉得它就是个简单的“过滤”动作。后来在项目中踩过几次坑,才发现里面的门道不少。
举个例子。一张订单表按月份分了12个分区。你查1月份的数据,数据库如果还去扫2月到12月的分区,那就是傻扫描。分区裁剪就是让数据库变聪明——只扫1月那个分区。
核心定义:分区裁剪(Partition Pruning)是查询优化器在执行计划生成阶段,根据查询条件中的分区键,提前排除不需要访问的分区,从而减少 I/O 和 CPU 消耗的优化技术。
3.1 裁剪的决策过程
分区裁剪不是查询执行时才发生的。它发生在优化器生成执行计划的阶段。我习惯把这个过程拆成三步:
- 解析查询条件——优化器先看 WHERE 子句里有没有分区键的条件
- 计算分区范围——根据条件值,算出需要访问哪些分区
- 裁剪执行计划——在计划中只保留目标分区,去掉无关分区
你想想看,如果查询条件里没有分区键,那优化器也没办法裁剪。这就是为什么我总跟团队说:分区键一定要出现在 WHERE 条件里,否则分区等于白分。
个人经验:我曾经接手过一个慢查询优化项目。一张10亿行的日志表按天分区,但开发人员写查询时只用了 log_type 做过滤条件,没带日期。结果每次查询都扫全部分区,性能惨不忍睹。加上日期条件后,查询时间从30秒降到了0.5秒。
3.2 裁剪的两种模式
根据分区键条件的类型,裁剪分为两种模式:
| 模式 | 触发条件 | 示例 | 裁剪效果 |
|---|---|---|---|
| 静态裁剪 | 分区键条件为常量 | WHERE dt = '2024-01-15' |
精确匹配单个分区 |
| 动态裁剪 | 分区键条件为子查询或参数 | WHERE dt IN (SELECT max_date FROM ...) |
运行时确定分区范围 |
静态裁剪是最理想的。优化器在编译阶段就能确定分区范围,执行计划里直接写死。动态裁剪稍微复杂一点,需要等到执行时才能确定具体分区。不过,能裁剪总比不裁剪好。
注意:动态裁剪虽然有用,但有些数据库(比如早期版本的 MySQL)对子查询的裁剪支持不完善。我建议尽量用静态条件,或者用 JOIN 替代子查询。
3.3 执行计划中的分区裁剪标识
怎么知道数据库有没有做分区裁剪?看执行计划。不同数据库的标识方式不同,但思路是一样的。
PostgreSQL 示例
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders
WHERE order_date = '2024-01-15';
-- 输出片段
Append
Subplans Removed: 11 ← 这就是裁剪标识
-> Seq Scan on orders_202401
Filter: (order_date = '2024-01-15'::date)
看到 Subplans Removed: 11 了吗?这说明原本有12个分区,优化器裁剪掉了11个,只留了1个。我个人习惯先看这个数字,如果它等于0,那就要警惕了——查询可能没走分区裁剪。
MySQL 示例
EXPLAIN PARTITIONS
SELECT * FROM orders
WHERE order_date = '2024-01-15';
-- 输出片段
+----+-------------+--------+------------+------+...
| id | select_type | table | partitions | type |...
+----+-------------+--------+------------+------+...
| 1 | SIMPLE | orders | p202401 | ref |...
+----+-------------+--------+------------+------++
MySQL 的 partitions 列会显示实际访问的分区名。如果这里显示 p202401,说明只扫了1月分区。如果显示 p202401,p202402,...,p202412,那就是全扫了。
避坑指南:我曾经遇到一个案例,执行计划里显示 Subplans Removed: 0,但查询条件明明带了分区键。排查后发现是分区键的数据类型和条件值不匹配——分区键是 DATE 类型,条件传的是字符串,隐式转换导致索引失效,裁剪也没生效。所以,类型一致是分区裁剪的前提。
3.4 分区裁剪的决策流程图
下面这张图展示了优化器做分区裁剪的完整决策流程。我把它画出来,方便你理解。
3.5 裁剪失效的常见场景
分区裁剪不是万能的。我总结了几种常见的失效场景,你写查询时要注意避开:
- 分区键被函数包裹——比如
WHERE DATE(partition_col) = '2024-01-15',优化器无法识别 - 隐式类型转换——分区键是
INT,条件传字符串'123' - 分区键不在 WHERE 中——这个最基础,但也最容易犯
- 使用 OR 连接非分区键条件——比如
WHERE partition_key = 'A' OR other_col = 'B' - 子查询嵌套过深——某些数据库对多层子查询的裁剪支持有限
重要提醒:我曾经在生产环境见过一个案例,开发人员在分区键上用了 TO_CHAR(partition_col, 'YYYY-MM-DD') 做格式化,然后跟字符串比较。结果分区裁剪完全失效,查询跑了20分钟。改成 partition_col = TO_DATE('2024-01-15', 'YYYY-MM-DD') 后,查询秒回。
3.6 如何验证裁剪是否生效
我个人的工作习惯是,每次写完分区表查询,都会做三步验证:
- 看执行计划——检查是否有
Subplans Removed或partitions列 - 看扫描行数——如果扫描行数远小于总行数,说明裁剪生效
- 看执行时间——裁剪后的查询应该比全表扫描快一个数量级
这三步走完,基本就能确认分区裁剪有没有正常工作。如果发现裁剪没生效,优先检查分区键的条件写法。
小技巧:在 PostgreSQL 中,我经常用 EXPLAIN (ANALYZE, BUFFERS, SUMMARY) 来查看分区裁剪的详细信息。如果 Buffers: shared hit= 的值很小,说明只访问了少量数据块,裁剪大概率是生效的。