使用分区裁剪优化扫表

更新时间:
复制 MD 格式

分区裁剪(Partition Pruning)是SelectDB查询优化器的核心能力之一。通过在查询条件中包含分区列的过滤条件,优化器能自动跳过不相关的分区,大幅减少数据扫描量。本文介绍分区裁剪的原理、生效条件和优化方法。

分区设计要点

分区裁剪能否生效,取决于建表时的分区设计。合理的分区策略是裁剪优化的前提。

选择合适的分区列

  • 选择查询中频繁出现在WHERE条件中的列作为分区列,通常为时间列(如event_timeorder_date)。

  • 分区列应具有较高的区分度,使查询条件能有效缩小扫描范围。

使用AUTO PARTITION自动分区(推荐)

通过AUTO PARTITION在数据写入时按需自动创建分区,无需预创建,适合分区键取值范围不确定或分区粒度灵活的场景:

-- 自动分区示例:写入时按天自动创建分区,无需预创建
CREATE TABLE user_events (
    event_time DATETIME NOT NULL,
    user_id BIGINT NOT NULL,
    event_type VARCHAR(32),
    event_data TEXT
)
DUPLICATE KEY(event_time, user_id)
AUTO PARTITION BY RANGE (date_trunc(event_time, 'day')) ()
DISTRIBUTED BY HASH(user_id) BUCKETS AUTO;

使用动态分区管理生命周期

对于分区生命周期管理有明确需求的时序数据(如保留最近N天分区、自动清理过期分区),配置动态分区:

-- 动态分区示例:自动管理分区生命周期(保留最近30天,预创建未来3天)
CREATE TABLE user_events_dynamic (
    event_time DATETIME NOT NULL,
    user_id BIGINT NOT NULL,
    event_type VARCHAR(32),
    event_data TEXT
)
DUPLICATE KEY(event_time, user_id)
PARTITION BY RANGE(event_time) ()
DISTRIBUTED BY HASH(user_id) BUCKETS AUTO
PROPERTIES (
    "dynamic_partition.enable" = "true",
    "dynamic_partition.time_unit" = "DAY",
    "dynamic_partition.start" = "-30",
    "dynamic_partition.end" = "3",
    "dynamic_partition.prefix" = "p"
);

控制分区粒度

单表分区数建议在数千以内。过细的分区(如按秒)会增加元数据管理开销;过粗的分区(如按年)会降低裁剪效果,导致单次查询仍需扫描大量数据。时序数据通常按天或月分区。

分区裁剪原理

SelectDB的分区裁剪在查询计划生成阶段进行。优化器根据WHERE条件中对分区列的过滤表达式,确定需要扫描的分区集合:

  • Range分区:优化器通过比较过滤条件与分区范围来确定命中的分区。

  • List分区:优化器通过过滤条件中的等值或IN条件匹配分区值列表。

可以通过EXPLAIN查看查询计划中的partitions字段确认分区裁剪是否生效:

EXPLAIN SELECT * FROM orders WHERE order_date = '2024-01-15';

-- 查看输出中的 partitions 字段
-- partitions=1/30 表示从30个分区中只扫描了1个分区

生效条件

分区裁剪在以下条件下生效:

条件类型

是否生效

示例

等值过滤

WHERE dt = '2024-01-01'

范围过滤

WHERE dt BETWEEN '2024-01-01' AND '2024-01-31'

IN列表

WHERE dt IN ('2024-01-01', '2024-01-02')

函数包裹分区列

WHERE YEAR(dt) = 2024

隐式类型转换

可能不生效

WHERE dt = 20240101(字符串列与数值比较)

OR组合非分区列

可能不生效

WHERE dt = '2024-01-01' OR user_id = 100

优化建议

直接使用分区列作为过滤条件

-- 推荐:直接使用分区列
SELECT * FROM orders WHERE order_date >= '2024-01-01' AND order_date < '2024-02-01';

-- 不推荐:函数包裹分区列导致无法裁剪
SELECT * FROM orders WHERE DATE_FORMAT(order_date, '%Y-%m') = '2024-01';

确保过滤值类型与分区列一致

-- 推荐:使用与分区列一致的DATE类型
SELECT * FROM orders WHERE order_date = DATE '2024-01-15';

-- 避免:传入字符串可能导致隐式转换
SELECT * FROM orders WHERE order_date = 20240115;

Join查询中的分区裁剪

Join查询中,确保每张分区表的过滤条件都包含分区列:

-- 两张表都能进行分区裁剪
SELECT a.*, b.product_name
FROM orders a JOIN products b ON a.product_id = b.product_id
WHERE a.order_date >= '2024-01-01'
  AND a.order_date < '2024-02-01'
  AND b.category = 'electronics';

验证分区裁剪效果

使用EXPLAIN VERBOSE查看详细的分区裁剪信息:

EXPLAIN VERBOSE SELECT count(*) FROM orders WHERE order_date = '2024-01-15';

-- 关注输出中:
-- TABLE: orders(orders), PREAGGREGATION: ON
-- partitions=1/365    -- 从365个分区中裁剪到1个
-- tablets=10/3650     -- 对应tablet数量也大幅减少

如果partitions显示的分区数等于总分区数,说明分区裁剪未生效,需要检查WHERE条件是否满足生效条件。