建一张按年份RANGE分区、30万行的表,用EXPLAIN验证日期范围查询只涉及单个分区,再对比一个反直觉的耗时结果。
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
查询只过滤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这两个分区的数据算进这次查询的代价里。
按直觉,裁剪到一个分区应该比扫全表快——测出来的却不是这样。
结果(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跑出来的原始结果。
EXPLAIN FORMAT=JSON / information_schema.PARTITIONS:验证裁剪效果