bigywater.tech
MySQL / InnoDB · 21

分区裁剪:日期条件到底读了几个分区

建一张按年份RANGE分区、30万行的表,用EXPLAIN验证日期范围查询只涉及单个分区,再对比一个反直觉的耗时结果。

100098≈99994
行数估算精确对应单分区大小
反而慢3倍
裁剪查询比无条件COUNT(*)更慢
裁剪≠索引
分区键不等于分区内部的排序依据

做的事:按年份分区,插入横跨3年的30万行

RANGE分区把数据物理分成几组独立的文件。

建表插入CREATE TABLE t8 (id INT AUTO_INCREMENT, d DATE, v INT, PRIMARY KEY(id, d)) PARTITION BY RANGE (YEAR(d)) ( PARTITION p2024 VALUES LESS THAN (2025), PARTITION p2025 VALUES LESS THAN (2026), PARTITION p2026 VALUES LESS THAN (2027) ); 插入30万行,均匀分布在2024/2025/2026三年 SELECT PARTITION_NAME, TABLE_ROWS FROM information_schema.PARTITIONS → p2024 100249 → p2025 99994 → p2026 99757

验证:EXPLAIN的行数估算,精确对应单个分区

查询只过滤2025年的日期,看优化器认为要扫描多少行。

结果(EXPLAIN FORMAT=JSON)SELECT COUNT(*) FROM t8 WHERE d BETWEEN '2025-01-01' AND '2025-12-31' "operation": "Covering index scan on t8 using PRIMARY" "estimated_rows": 100098.0 ← 几乎精确等于p2025分区的行数(99994),不是30万

优化器给出的行数估算是100098,跟p2025分区的99994行几乎完全对上——如果没有发生分区裁剪,这个数字应该接近30万(全表)。这就是分区裁剪生效的证据:优化器压根没有把p2024、p2026这两个分区的数据算进这次查询的代价里。

意外发现:裁剪后的查询,反而比无条件COUNT(*)慢

按直觉,裁剪到一个分区应该比扫全表快——测出来的却不是这样。

结果(3次重复,单位ms)WHERE d BETWEEN '2025-01-01' AND '2025-12-31'(裁剪到单分区+过滤) → 25.9 / 25.5 / 25.3 无条件SELECT COUNT(*) FROM t8(全表,无WHERE) → 6.37 / 6.87 / 9.2

结果反直觉:裁剪到单个分区的查询反而比无条件COUNT(*)慢了约3倍。原因是:PRIMARY KEY(id, d) 里 d 不是第一列,分区内部数据并不是按 d 排序的,命中p2025分区之后,仍然要对这10万行逐行判断日期范围条件;而无条件的 COUNT(*) 可以走一种更轻量的"直接读取各分区元数据行数再加总"的捷径,压根不用真的扫描任何一行数据。

这是分区裁剪容易被高估的地方:分区裁剪解决的是"跳过不相关分区的文件",但裁剪之后,命中的那个分区内部该怎么定位数据,还是要看这个分区内部有没有合适的索引/排序——分区键本身如果不是查询过滤列的天然排序依据,裁剪省下来的只是"跳过其它分区"这一部分,分区内部的扫描代价该有多少还是多少,不会自动变成"秒级返回"。

分步演示:分区裁剪省下的和没省下的

每一步都是本文上面用EXPLAIN和EXPLAIN ANALYZE跑出来的原始结果。

分区裁剪省的是跳过分区,不是分区内部的扫描代价
这一步新验证的结果

    本文用到的工具

    • 本机 MySQL 26.7.0(InnoDB),30万行RANGE分区表
    • EXPLAIN FORMAT=JSON / information_schema.PARTITIONS:验证裁剪效果
    京ICP备2026067502号-1京公网安备11010502063487号