假设你的表结构是这样的:
CREATE TABLE `mytable` (
`Location` varchar(50) DEFAULT NULL,
`item` varchar(50) DEFAULT NULL,
`cost` decimal(14,2) DEFAULT NULL,
`date` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
如您所见,表格上没有设置索引。我在本地数据库中创建了一个假表并插入了 500K 行数据。对于您的查询,我在它运行了 10 分钟后停止了,因为老实说,这足以说明查询效率低下。然后我改成JOIN的方式,所以修改后的查询是这样的:
SELECT t1.location, t1.item, MAX(t1.cost) AS cost,
t1.date
FROM MyTable AS t1
JOIN (SELECT MAX(t2.date) t2d, t2.location, t2.item
FROM MyTable AS t2
WHERE t2.date <= '2021/10/31'
GROUP BY t2.location, t2.item) ref
ON t1.date = ref.t2d
AND t1.location = ref.location
AND t1.item = ref.item
WHERE t1.cost <> 0
GROUP BY t1.location, t1.item, t1.date;
仍然使用与上面相同的表结构(没有索引)和 500K 的数据,这个查询返回我:
1 queries executed, 1 success, 0 errors, 0 warnings
303 row(s) affected
Execution Time : 0.531 sec
Transfer Time : 0 sec
Total Time : 0.532 sec
在不到 1 秒的时间内,总共 500K 数据中的 303 行。但是,与总的 500K 相比,303 行只是一点点,对吧?因此,我已将表中的大部分 date 列全部更新到 '2021/10/31' 之前并再次测试查询,结果如下:
1 queries executed, 1 success, 0 errors, 0 warnings
416507 row(s) affected
Execution Time : 16.413 sec
Transfer Time : 0.208 sec
Total Time : 16.621 sec
查询在大约 16 秒内返回 500K 中的 416,507 条数据。并不是很快,但请记住这是在没有索引的表上。让我们用索引试试这个。现在,我不打算对列中的一列进行索引,然后也一一进行测试,而是一次性为每一列分配其自己的索引并进行测试。添加索引的语法:
ALTER TABLE mytable
ADD INDEX (Location),
ADD INDEX (item),
ADD INDEX (cost),
ADD INDEX (date)
以及创建索引后的查询结果:
1 queries executed, 1 success, 0 errors, 0 warnings
416507 row(s) affected
Execution Time : 7.753 sec
Transfer Time : 0.220 sec
Total Time : 7.973 sec
返回时间是在列上建立索引之前的一半。查询的执行计划如下所示:
| id |
select_type |
table |
type |
possible_keys |
key |
key_len |
ref |
rows |
Extra |
| 1 |
PRIMARY |
|
ALL |
NULL |
NULL |
NULL |
NULL |
248559 |
Using where; Using temporary; Using filesort |
| 1 |
PRIMARY |
t1 |
ref |
Location,item,cost,date |
Location |
53 |
ref.location |
1 |
Using where |
| 2 |
DERIVED |
t2 |
ALL |
Location,item,date |
NULL |
NULL |
NULL |
497119 |
Using where; Using temporary; Using filesort |
最后,让我们回到您的原始查询,看看索引对其性能的影响有多大。索引到位后,您的原始查询将返回:
1 queries executed, 1 success, 0 errors, 0 warnings
Query: SELECT t1.location, t1.item, MAX(t1.cost) AS cost,
t1.date
FROM MyTable AS t1
WHERE t1.date = (SELECT MAX(t2.date)
FROM MyTable AS t2
WHERE t1.location = t2.location
AND t1.item = t2.item
AND t1.cost <> 0
AND t2.date <= '2021/10/31')
GROUP BY t1.location, t1.item, t1.date;
416507 row(s) affected
Execution Time : 9.840 sec
Transfer Time : 0.202 sec
Total Time : 10.042 sec
以及执行计划:
| id |
select_type |
table |
type |
possible_keys |
key |
key_len |
ref |
rows |
Extra |
| 1 |
PRIMARY |
t1 |
ALL |
NULL |
NULL |
NULL |
NULL |
497119 |
Using where; Using temporary; Using filesort |
| 2 |
DEPENDENT SUBQUERY |
t2 |
ref |
Location,item,date |
Location |
53 |
test.t1.Location |
1 |
Using where |
作为结论,如果您的表上没有索引,您的原始查询肯定需要永远运行,而将其转换为 JOIN 会显着减少持续时间。设置索引后,JOIN 查询比您的原始查询快 2-3 秒,但最重要的是要注意,您的原始查询甚至难以在表有索引之前返回仅 303 行的数据结果。您还没有发布您的表结构,我确实发布了一条(现已删除)评论要求它但是在自己进行测试之后,我很确定您当前的表没有任何索引。
P/S:这个测试数据对于小提琴来说太大了,所以我在 Pastebin 中有测试代码。您可以在本地服务器上对此进行测试。插入数据语法仅兼容 MySQL v8+ 和 MariaDB 10.2.2 及以上版本。
https://pastebin.com/GdXhQ5mw