Mysql 分区查询比不分区查询花费更多时间

Mysql query with partition taking more time than without partitioning

我有一个有 30 万行的 table。 table 非常重,因此每次查询都会变慢。在尝试了很多索引和其他优化之后,我决定在 table.

上创建分区

现在我有 3 个版本 table

  1. e_update
  2. e_update_partition(20分区使用HASH(on event_id))
  3. e_update_partition_event(12 个分区,每个分区有 25K 个条目(在 event_id 上))

现在我 运行 每个 table 一个一个地查询相同的查询并比较时间

SELECT eu.event_id
FROM e_update eu
INNER JOIN event e ON e.id=eu.event_id
WHERE eu.start_date > 2010-10-15
  AND e.published=1
  AND eu.event_id > 25000
  AND eu.event_id < 50000;

耗时 - 集合中有 189911 行,2 个警告(14.43 秒)

SELECT eu.event_id
FROM e_update_partition eu
INNER JOIN event e ON e.id=eu.event_id
WHERE eu.start_date > 2010-10-15
  AND e.published=1
  AND eu.event_id > 25000
  AND eu.event_id < 50000;

耗时 - 集合中有 189911 行,2 个警告(15.87 秒)

解释结果-

+----+-------------+-------+-----------------------------------------------------------------------+-------+--------------------------------+-----------+---------+--------------------+--------+-----------------------+
| id | select_type | table | partitions                                                            | type  | possible_keys                  | key       | key_len | ref                | rows   | Extra                 |
+----+-------------+-------+-----------------------------------------------------------------------+-------+--------------------------------+-----------+---------+--------------------+--------+-----------------------+
|  1 | SIMPLE      | e     | NULL                                                                  | range | PRIMARY,published              | published | 6       | NULL               | 120674 | Using index condition |
|  1 | SIMPLE      | eu    | p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16,p17,p18,p19 | ref   | event_id,start_date,event_id_2 | event_id  | 4       | biztradeshows.e.id |      1 | Using where           |
+----+-------------+-------+-----------------------------------------------------------------------+-------+--------------------------------+-----------+---------+--------------------+--------+-----------------------+

SELECT eu.event_id
FROM e_update_partition_event eu
INNER JOIN event e ON e.id=eu.event_id
WHERE eu.start_date > 2010-10-15
  AND e.published=1
  AND eu.event_id > 25000
  AND eu.event_id < 50000;

耗时 - 集合中有 189911 行,2 条警告(20.56 秒)

解释结果-

+----+-------------+-------+----------------------------------+--------+--------------------------------+-----------+---------+--------------------+--------+-----------------------+
| id | select_type | table | partitions                       | type   | possible_keys                  | key       | key_len | ref                | rows   | Extra                 |
+----+-------------+-------+----------------------------------+--------+--------------------------------+-----------+---------+--------------------+--------+-----------------------+
|  1 | SIMPLE      | e     | NULL                             | range  | PRIMARY,published              | published | 6       | NULL               | 120674 | Using index condition |
|  1 | SIMPLE      | eu    | p3,p4,p5,p6,p7,p8,p9,p10,p11,p12 | eq_ref | event_id,start_date,event_id_2 | event_id  | 4       | biztradeshows.e.id |      1 | Using where           |
+----+-------------+-------+----------------------------------+--------+--------------------------------+-----------+---------+--------------------+--------+-----------------------+

第三个查询的分区模式

(PARTITION p1 VALUES LESS THAN (25000) ENGINE = InnoDB,
PARTITION p2 VALUES LESS THAN (50000) ENGINE = InnoDB,
PARTITION p3 VALUES LESS THAN (75000) ENGINE = InnoDB,
PARTITION p4 VALUES LESS THAN (100000) ENGINE = InnoDB,
PARTITION p5 VALUES LESS THAN (125000) ENGINE = InnoDB,
PARTITION p6 VALUES LESS THAN (150000) ENGINE = InnoDB,
PARTITION p7 VALUES LESS THAN (175000) ENGINE = InnoDB,
PARTITION p8 VALUES LESS THAN (200000) ENGINE = InnoDB,
PARTITION p9 VALUES LESS THAN (225000) ENGINE = InnoDB,
PARTITION p10 VALUES LESS THAN (250000) ENGINE = InnoDB,
PARTITION p11 VALUES LESS THAN (275000) ENGINE = InnoDB,
PARTITION p12 VALUES LESS THAN (300000) ENGINE = InnoDB)

为什么我的第 3 个查询比其他两个查询花费更多时间并且几乎使用了所有分区?

再多的分区也无济于事:

e.published=1 

布尔字段无法有效地建立索引。为什么?因为它们只有两个值之一。这看起来像一个 mutable 字段(您对其进行更新的字段,因为 published 可能会打开和关闭)。这样的字段也不能用于分区。

您的第一个选择是将此 published 字段与另一个字段组合并创建一个复合索引,并希望它具有足够的基数以成为有用的索引。

您的第二个选择是创建存档 table 并将未发布的项目移出存档 table。

顺便说一句,您的查询有一个没有多大意义的条件:

 and eu.event_id >25000 and eu.event_id>50000;

这可以缩短为

 and eu.event_id > 50000;

更新

为什么查询的是所有分区?那么你的第一个分区方案是 hash partitioning

Partitioning by HASH is used primarily to ensure an even distribution of data among a predetermined number of partitions.

所以你的数据在所有分区

第二种方案,仔细观察会发现有两个分区没有被使用。这些是您的 where 子句遗漏的分区。

所以问题出在你的 Where 子句中:-)

BY HASH没用

event_id > ...BY HASH(event_id) 是一个完全无用的组合。散列不知道哪个值将在哪个分区中,除非一个接一个。因此,它只是假设需要所有分区。

然后,它必须打开每个分区,执行查找,通常在那里找不到任何想要的值,然后转到下一个分区。因此,使用 PARITIIONing 比没有使用 更多 时间。即使 event_id 上没有索引,非分区版本可能会稍微快一些。使用 INDEX(event_id),非分区版本可能会快很多。

我还没有找到 BY HASH 提供任何性能优势的用例。

非分区选项 1

对于您提出的一个查询,我的第一个猜测是不分区,但我会

INDEX(start_date),
INDEX(event_id)

优化器会查看其微不足道的统计数据并在它们之间进行选择。

非分区选项 2

同样,假设 那个 查询,我的第二个猜测是这个 "covering" 索引:

INDEX(start_date, event_id)

关于分区的提示:甚至不要考虑小于一百万行的表。

More 讨论。

二维分区

该查询本质上是一个二维问题,因为有两个 "ranges"。但是要使分区有用,您必须使用 BY RANGE,而不是 BY HASH。因此,按

 BY RANGE(TO_DAYS(start_date))  together with
 PRIMARY KEY(event_id, ..., start_date)

 BY RANGE(event_id)  together with
 PRIMARY KEY(start_date, ..., event_id)

一定要使用InnoDB,在PK上利用它的集群优势。 (上面我的 link 讨论了将移动时间作为分区键的一些问题。)