Mysql 分区查询比不分区查询花费更多时间
Mysql query with partition taking more time than without partitioning
我有一个有 30 万行的 table。 table 非常重,因此每次查询都会变慢。在尝试了很多索引和其他优化之后,我决定在 table.
上创建分区
现在我有 3 个版本 table
- e_update
- e_update_partition(20分区使用HASH(on event_id))
- 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 讨论了将移动时间作为分区键的一些问题。)
我有一个有 30 万行的 table。 table 非常重,因此每次查询都会变慢。在尝试了很多索引和其他优化之后,我决定在 table.
上创建分区现在我有 3 个版本 table
- e_update
- e_update_partition(20分区使用HASH(on event_id))
- 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 讨论了将移动时间作为分区键的一些问题。)