【发布时间】:2021-12-19 21:55:48
【问题描述】:
select MAX(id) from studenthistory
where class_id = 1
and date(created_at) = '2021-11-05'
and time(created_at) > TIME('04:00:00')
group by student_id
composite indexes = ("class_id", "student_id", "created_at")
id 是主键。
date(created_at) = '2021-11-05' and time(created_at) > TIME('04:00:00') 过滤条件对于 Max 函数是否不必要,因为 studenthistory 已在 class_id 和 student_id 上建立索引?
我添加日期时间过滤器的唯一原因是该表会随着时间的推移变得越来越大。 (历史数据)我想减少查询必须搜索的行数。
但是对于 Max 函数的情况 - 我相信 MAX 会简单地获取最后一个值而不检查整行,如果它被索引的话。
所以我可以安全地删除日期时间过滤器并将其变成
select MAX(id) from studenthistory
where class_id = 1
group by student_id
并且有同样的表现? (或者更好,因为它不需要进一步过滤?)
检查查询计划似乎性能相似,但表的大小目前相当小..
第一:
| -> Group aggregate: max(id) (cost=1466.30 rows=7254) (actual time=2.555..5.766 rows=3 loops=1)
-> Filter: ((cast(studenthistory.created_at as date) = '2021-11-05') and (cast(riderlocation.created_at as time(6)) > <cache>(cast('04:00:00' as time)))) (cost=740.90 rows=7254) (actual time=0.762..5.384 rows=5349 loops=1)
-> Index lookup on studenthistory using idx_studenthistory_class_id_931474 (class_id=1) (cost=740.90 rows=7254) (actual time=0.029..3.589 rows=14638 loops=1)
|
1 row in set (0.00 sec)
第二:
| -> Group aggregate: max(studenthistory.id) (cost=1475.40 rows=7299) (actual time=0.545..5.271 rows=10 loops=1)
-> Index lookup on studenthistory using idx_studenthistory_class_id_931474 (class_id=1) (cost=745.50 rows=7299) (actual time=0.026..4.164 rows=14729 loops=1)
|
1 row in set (0.01 sec)
在此先感谢
更新:应用@rick james 的建议:
将索引更改为 (class_id, student_id, id)。
FLUSH STATUS;
explain FORMAT=JSON SELECT MAX(`id`) `0` FROM `studenthistory`
WHERE `class_id`=1 AND `created_at`>='2021-11-05T18:25:50.544850+00:00'
GROUP BY `student_id`;
| {
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "940.10"
},
"grouping_operation": {
"using_filesort": false,
"table": {
"table_name": "studenthistory",
"access_type": "ref",
"possible_keys": [
"fk_studenthist_student_e25b0310",
"idx_studenthistory_class_id_931474"
],
"key": "idx_studenthistory_class_id_931474",
"used_key_parts": [
"class_id"
],
"key_length": "4",
"ref": [
"const"
],
"rows_examined_per_scan": 8381,
"rows_produced_per_join": 2793,
"filtered": "33.33",
"cost_info": {
"read_cost": "102.00",
"eval_cost": "279.34",
"prefix_cost": "940.10",
"data_read_per_join": "130K"
},
"used_columns": [
"id",
"created_at",
"student_id",
"class_id"
],
"attached_condition": "(`test-table`.`studenthistory`.`created_at` >= TIMESTAMP'2021-11-05 18:25:50.54485')"
}
}
}
} |
即只有 class_id 用作索引,(因为 created_at 不再在索引中。由于过滤器,rows_produced_per_join 较低:2793,
没有日期时间过滤器:
FLUSH STATUS;
mysql> explain FORMAT=JSON SELECT MAX(`id`) `0` FROM `studenthistory`
WHERE `class_id`=1 GROUP BY `student_id`;
| {
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "854.75"
},
"grouping_operation": {
"using_filesort": false,
"table": {
"table_name": "studenthistory",
"access_type": "ref",
"possible_keys": [
"fk_studenthistory_student_e25b0310",
"idx_studenthistory_class_id_931474"
],
"key": "idx_studenthistory_class_id_931474",
"used_key_parts": [
"class_id"
],
"key_length": "4",
"ref": [
"const"
],
"rows_examined_per_scan": 8381,
"rows_produced_per_join": 8381,
"filtered": "100.00",
"using_index": true,
"cost_info": {
"read_cost": "16.65",
"eval_cost": "838.10",
"prefix_cost": "854.75",
"data_read_per_join": "392K"
},
"used_columns": [
"id",
"student_id",
"class_id"
]
}
}
}
} |
在所有 3 个索引(“class_id”、“student_id”、“id”)上运行,相同的 8381 行数略低查询成本(940 -> 854)
使用原始索引(“class_id”、“student_id”、“created_at”)应用第一个查询会产生:
FLUSH STATUS;
explain FORMAT=JSON SELECT MAX(`id`) `0` FROM `studenthistory`
WHERE `class_id`=1 AND `created_at`>='2021-11-05T18:25:50.544850+00:00'
GROUP BY `student_id`;
| {
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "858.94"
},
"grouping_operation": {
"using_filesort": false,
"table": {
"table_name": "studenthistory",
"access_type": "ref",
"possible_keys": [
"fk_studenthistory_student_e25b0310",
"idx_studenthistory_class_id_931474"
],
"key": "idx_studenthistory_class_id_931474",
"used_key_parts": [
"class_id"
],
"key_length": "4",
"ref": [
"const"
],
"rows_examined_per_scan": 8381,
"rows_produced_per_join": 2793,
"filtered": "33.33",
"using_index": true,
"cost_info": {
"read_cost": "20.84",
"eval_cost": "279.34",
"prefix_cost": "858.94",
"data_read_per_join": "130K"
},
"used_columns": [
"id",
"created_at",
"student_id",
"class_id"
],
"attached_condition": "(`test-table`.`studenthistory`.`created_at` >= TIMESTAMP'2021-11-05 18:25:50.54485')"
}
}
}
} |
这次的成本是 858,行 "rows_examined_per_scan": 8381, "rows_produced_per_join": 2793。但是只有 class_id 被用作键。 (为什么。?)不是剩余的 student_id 和 created_at
【问题讨论】:
-
你很困惑。不要再混淆了。如果查询产生正确的结果:它可能是正确的。性能不是问题。正确性是。
-
你为什么有
and time(created_at) > TIME('00:00:00')?为什么要排除在任何日期恰好在午夜创建的记录? -
我相信您的复合索引应该允许在不扫描的情况下完成查询。也许更好的是
(student_id, class_id, id) -
“MAX(id) 已经在 class_id 和 student_id 上建立索引”是什么意思?
MAX(id)不是表格;它不能被索引。您的意思是说studenthistory已在这些列上编入索引吗? -
@Barmar,那个时间只是一个例子(以及表名和列名)。这个查询将在每秒插入90k个设备(传感器数据)的表上运行~每 10 秒一次,具体取决于设备类型。在实际情况下,这将是“最后 12 小时”。谢谢。