为什么这个简单的查询在 mysql 使用分区时不是 运行 最优?
Why this simple query does not run optimal in mysql using partitions?
在Mysql 5.7.31 版本,Ubuntu 18.04 8core cpu 系统和 24gb 内存下,我有一个 table 名为 mytable。
在名为 created_at 的日期时间字段上使用 HASH(YEAR(created_at)) 函数将其划分为 30 个分区。它总共包含 185378420 条记录。它有一个使用 auto_increment 名为 id 的主键。
Table结构:
CREATE TABLE `mytyable` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`,`created_at`),
KEY `created_at` (`created_at`),
) ENGINE=MyISAM AUTO_INCREMENT=194156422 DEFAULT CHARSET=utf8
/*!50100 PARTITION BY HASH (YEAR(created_at))
PARTITIONS 30 */
我正在尝试 运行 这个简单的查询。
SELECT * FROM `mytable` WHERE `id` IN (194070462,194070461) ORDER BY `id` DESC;
我不明白为什么它会卡在发送数据阶段,同时我不确定它是否成功完成。
另一方面,如果我更改排序方向,它 运行 非常快(以毫秒计)。
SELECT * FROM `mytable` WHERE `id` IN (194070462,194070461) ORDER BY `id` ASC;
而且如果我完全删除 order by 子句
SELECT * FROM `mytable` WHERE `id` IN (194070462,194070461);
两个查询都产生了预期的输出:
+-----------+---------------------+
| id | created_at |
+-----------+---------------------+
| 194070461 | 2021-07-26 21:16:47 |
| 194070462 | 2021-07-26 21:16:47 |
+-----------+---------------------+
我相信它应该能够先收集数据,然后尝试对它们进行排序。
所有 3 个查询的解释完全相同:
+----+-------------+-------+---------------------------------------------------------------------------------------------------------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+---------------------------------------------------------------------------------------------------------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | mytable | p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16,p17,p18,p19,p20,p21,p22,p23,p24,p25,p26,p27,p28,p29 | range | PRIMARY | PRIMARY | 4 | NULL | 20 | 100.00 | Using index condition |
+----+-------------+-------+---------------------------------------------------------------------------------------------------------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
知道幕后发生了什么吗?由于范围查询和 order by desc 子句的组合,它是否以某种方式存在分区限制?有什么解决办法吗?
{
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "9.62"
},
"ordering_operation": {
"using_filesort": false,
"table": {
"table_name": "mytable",
"partitions": [
"p0",
"p1",
"p2",
"p3",
"p4",
"p5",
"p6",
"p7",
"p8",
"p9",
"p10",
"p11",
"p12",
"p13",
"p14",
"p15",
"p16",
"p17",
"p18",
"p19",
"p20",
"p21",
"p22",
"p23",
"p24",
"p25",
"p26",
"p27",
"p28",
"p29"
],
"access_type": "range",
"possible_keys": [
"PRIMARY"
],
"key": "PRIMARY",
"used_key_parts": [
"id"
],
"key_length": "4",
"rows_examined_per_scan": 20,
"rows_produced_per_join": 20,
"filtered": "100.00",
"using_index": true,
"cost_info": {
"read_cost": "5.62",
"eval_cost": "4.00",
"prefix_cost": "9.62",
"data_read_per_join": "11K"
},
"used_columns": [
"id",
"created_at"
],
"attached_condition": "(`mydb`.`mytable`.`id` in (194070462,194070461))"
}
}
}
}
在我看来,PARTITION BY HASH
完全没用,尤其是对于性能而言。 SUBPARTITIONing
和 BY RANGE
以外的任何方法同上。即便如此,也很少有它有用的情况。
你的情况...
id
是PRIMARY KEY
(或者至少是PK或某个二级索引中的第一个列?如果是,那么:
PARTITIONing
除 id
以外的任何其他方式都将涉及查看 每个 分区。当然,一旦它存在,它就会使用一个索引。但这是找到 2 行的 60 次查找。 (60 = IN 中的 2 个项目乘以 30 个分区)。显然,它甚至比这更糟糕——因为它被“卡住了”,正如你所说的那样。
如果没有分区,这将是一个简单的 2 次查找。
PARTITION BY RANGE(YEAR(created_at))
会比 BY HASH
好,但也好不了多少。很少有查询可以有效利用“分区修剪”。哈希永远不能为修剪日期提供“范围”。
请告诉我 SHOW CREATE TABLE
以及应用的主要查询。我将就要使用的最佳索引集以及分区是否有任何好处提出建议。另一个问题:您会清除“旧”行吗? (通过 DROP PARTITION
清除是 BY RANGE
在约会时的主要用途。但这只会帮助 DELETE
,而不是 SELECTs
。)
改用InnoDB!
在Mysql 5.7.31 版本,Ubuntu 18.04 8core cpu 系统和 24gb 内存下,我有一个 table 名为 mytable。 在名为 created_at 的日期时间字段上使用 HASH(YEAR(created_at)) 函数将其划分为 30 个分区。它总共包含 185378420 条记录。它有一个使用 auto_increment 名为 id 的主键。
Table结构:
CREATE TABLE `mytyable` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`,`created_at`),
KEY `created_at` (`created_at`),
) ENGINE=MyISAM AUTO_INCREMENT=194156422 DEFAULT CHARSET=utf8
/*!50100 PARTITION BY HASH (YEAR(created_at))
PARTITIONS 30 */
我正在尝试 运行 这个简单的查询。
SELECT * FROM `mytable` WHERE `id` IN (194070462,194070461) ORDER BY `id` DESC;
我不明白为什么它会卡在发送数据阶段,同时我不确定它是否成功完成。
另一方面,如果我更改排序方向,它 运行 非常快(以毫秒计)。
SELECT * FROM `mytable` WHERE `id` IN (194070462,194070461) ORDER BY `id` ASC;
而且如果我完全删除 order by 子句
SELECT * FROM `mytable` WHERE `id` IN (194070462,194070461);
两个查询都产生了预期的输出:
+-----------+---------------------+
| id | created_at |
+-----------+---------------------+
| 194070461 | 2021-07-26 21:16:47 |
| 194070462 | 2021-07-26 21:16:47 |
+-----------+---------------------+
我相信它应该能够先收集数据,然后尝试对它们进行排序。
所有 3 个查询的解释完全相同:
+----+-------------+-------+---------------------------------------------------------------------------------------------------------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+---------------------------------------------------------------------------------------------------------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | mytable | p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16,p17,p18,p19,p20,p21,p22,p23,p24,p25,p26,p27,p28,p29 | range | PRIMARY | PRIMARY | 4 | NULL | 20 | 100.00 | Using index condition |
+----+-------------+-------+---------------------------------------------------------------------------------------------------------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
知道幕后发生了什么吗?由于范围查询和 order by desc 子句的组合,它是否以某种方式存在分区限制?有什么解决办法吗?
{
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "9.62"
},
"ordering_operation": {
"using_filesort": false,
"table": {
"table_name": "mytable",
"partitions": [
"p0",
"p1",
"p2",
"p3",
"p4",
"p5",
"p6",
"p7",
"p8",
"p9",
"p10",
"p11",
"p12",
"p13",
"p14",
"p15",
"p16",
"p17",
"p18",
"p19",
"p20",
"p21",
"p22",
"p23",
"p24",
"p25",
"p26",
"p27",
"p28",
"p29"
],
"access_type": "range",
"possible_keys": [
"PRIMARY"
],
"key": "PRIMARY",
"used_key_parts": [
"id"
],
"key_length": "4",
"rows_examined_per_scan": 20,
"rows_produced_per_join": 20,
"filtered": "100.00",
"using_index": true,
"cost_info": {
"read_cost": "5.62",
"eval_cost": "4.00",
"prefix_cost": "9.62",
"data_read_per_join": "11K"
},
"used_columns": [
"id",
"created_at"
],
"attached_condition": "(`mydb`.`mytable`.`id` in (194070462,194070461))"
}
}
}
}
PARTITION BY HASH
完全没用,尤其是对于性能而言。 SUBPARTITIONing
和 BY RANGE
以外的任何方法同上。即便如此,也很少有它有用的情况。
你的情况...
id
是PRIMARY KEY
(或者至少是PK或某个二级索引中的第一个列?如果是,那么:
PARTITIONing
除 id
以外的任何其他方式都将涉及查看 每个 分区。当然,一旦它存在,它就会使用一个索引。但这是找到 2 行的 60 次查找。 (60 = IN 中的 2 个项目乘以 30 个分区)。显然,它甚至比这更糟糕——因为它被“卡住了”,正如你所说的那样。
如果没有分区,这将是一个简单的 2 次查找。
PARTITION BY RANGE(YEAR(created_at))
会比 BY HASH
好,但也好不了多少。很少有查询可以有效利用“分区修剪”。哈希永远不能为修剪日期提供“范围”。
请告诉我 SHOW CREATE TABLE
以及应用的主要查询。我将就要使用的最佳索引集以及分区是否有任何好处提出建议。另一个问题:您会清除“旧”行吗? (通过 DROP PARTITION
清除是 BY RANGE
在约会时的主要用途。但这只会帮助 DELETE
,而不是 SELECTs
。)
改用InnoDB!