Mysql - 按月分区扫描所有分区而不是一个分区

Mysql - Monthwise Partitioning scanning all partitions instead of one

我创建了一个table如下:

CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired datetime NOT NULL DEFAULT '2000-01-01')
PARTITION BY RANGE ( Month(hired) ) (
PARTITION p1 VALUES LESS THAN (2),
PARTITION p2 VALUES LESS THAN (3),
PARTITION p3 VALUES LESS THAN (4),
PARTITION p4 VALUES LESS THAN (5),
PARTITION p5 VALUES LESS THAN (6),
PARTITION p6 VALUES LESS THAN (7),
PARTITION p7 VALUES LESS THAN (8),
PARTITION p8 VALUES LESS THAN (9),
PARTITION p9 VALUES LESS THAN (10),
PARTITION p10 VALUES LESS THAN (11),
PARTITION p11 VALUES LESS THAN (12),
PARTITION p12 VALUES LESS THAN maxvalue
);

正如你在上面看到的,按月划分已经完成。

接下来我向 table 添加几条记录。

插入记录后,我查询 table 以验证它是否正在从预期的分区中获取数据。

当我给出以下查询时,

EXPLAIN PARTITIONS SELECT COUNT(*) 
FROM employees 
WHERE hired BETWEEN'2015-01-01' AND '2015-03-01';

理想情况下,它必须扫描分区 p1、p2 和 p3。 但是explain结果显示是在扫描所有分区。

我将分区从按月改为按年略微更改如下:

ALTER TABLE employees partition BY range(Year(hired))
PARTITION p1 VALUES LESS THAN (2001),
PARTITION p2 VALUES LESS THAN (2005),
PARTITION p3 VALUES LESS THAN (2010),
PARTITION p4 VALUES LESS THAN (2015),
PARTITION p5 VALUES LESS THAN MAXVALUE);

现在我像以前一样查询:

EXPLAIN PARTITIONS SELECT COUNT(*)
FROM employees
WHERE hired BETWEEN '2015-01-01' and '2015-03-01';

结果显示它只从分区 p5 中获取数据。

我不知道为什么它适用于年分区而不是月分区。白天也观察到同样的问题。

请帮助我了解为什么 MySQL 会这样。

您发现了另一种 PARTITIONing 没有用的情况。

BETWEEN '2015-01-01' and '2015-03-01'; -- could have been optimized
BETWEEN '2015-01-01' and '2016-03-01'; -- must touch all partitions

分区修剪代码太笨了,无法区分两者。

即使修剪按预期工作,查询也不会比具有 INDEX(hired) 的非分区 table 快。您有任何可能效果更好的查询吗?

More rants and tips on PARTITION.

MySQL 关于“按月”分区与“按年”分区的行为记录在 MySQL 参考手册中:

http://dev.mysql.com/doc/refman/5.7/en/partitioning-pruning.html

节选

... optimization can be applied whenever the partitioning expression consists of an equality or a range which can be reduced to a set of equalities, or when the partitioning expression represents an increasing or decreasing relationship.

Pruning can also be applied for tables partitioned on a DATE or DATETIME column when the partitioning expression uses the YEAR() or TO_DAYS() function. In addition, in MySQL 5.7, pruning can be applied for such tables when the partitioning expression uses the TO_SECONDS() function.


我认为问题的根源在于 MONTH(datecol) 不是“升序”关系。如果您指定的范围是“2014-11-01”到“2015-02-01”,那将是分区 p11、p12,然后环绕到 p01、p02。

您查询的范围属于特例。

(请注意,按 YEAR() 和 TO_DAYS() do 进行分区表示升序关系。也就是说,列中较高的值永远不会回绕到较低的分区。)

要通过按月分区进行分区修剪,我认为您可以尝试在分区表达式上添加查询相等谓词。例如:

   WHERE MONTH(hiredate) IN (1,2)
     AND hiredate >= '2015-01-01'
     AND hiredate < '2015-03-01'

MySQL可能能够使用第一个条件进行分区修剪。

或者,性能可能会变得很糟糕,MySQL 在 table.

中的每个翻转行上计算表达式(MONTH() 函数)

无论分区方案如何,您都希望定义一个索引

 ... ON employees(hiredate)

有了这个,即使您没有进行任何分区修剪,检查每个分区也会有一点开销。但是执行范围检查不需要对分区中的每一行进行全扫描。 MySQL 可以对索引使用范围扫描操作,以有效地检查一个分区在很多分区中是否存在“此处未找到行”。

最重要的是,缺少适当的索引会降低性能。

分区不是提高查询性能的灵丹妙药。索引也不是灵丹妙药,但适当的索引比分区更珍贵、闪亮和子弹形。