我无法找出为什么我的查询非常慢;双Xeon L5630 60秒,48GB DDR3运行Ubuntu 16.04,PHP7.0-FPM和MariaDB 10.0.27
SELECT v.video_id, v.user_id, v.title, v.slug, v.rating, v.rated_by,
v.duration, v.thumb, v.total_views, v.total_comments, v.add_time,
v.view_time, v.status, v.source_id, v.orientation, v.thumbs,
v.featured, v.flagged,
u.username,
s.name,
f.reason,
GROUP_CONCAT(c.name) AS categories
FROM video AS v
LEFT JOIN video_flags AS f ON (f.video_id = v.video_id)
LEFT JOIN video_sources AS s ON (s.source_id = v.source_id)
LEFT JOIN user AS u ON (u.user_id = v.user_id)
LEFT JOIN video_category AS vc ON (vc.video_id = v.video_id)
LEFT JOIN video_categories AS c ON (c.category_id = vc.category_id) GROUP BY v.video_id ORDER BY v.video_id DESC LIMIT 10
我已经确定问题出在 video_flags 表中,因为当我在video_flags上评论f.reason字段和左连接时,查询只需要152ms。 video_flags表在video_id上有一个索引,两个表中的字段类型相同INT(11)
当我运行说明选择时,我得到以下回复:
+------+-------------+-------+--------+---------------+----------+---------+----------------------------+---------+-------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+--------+---------------+----------+---------+----------------------------+---------+-------------------------------------------------+
| 1 | SIMPLE | v | ALL | NULL | NULL | NULL | NULL | 1219933 | Using temporary; Using filesort |
| 1 | SIMPLE | f | ALL | video_id | NULL | NULL | NULL | 1 | Using where; Using join buffer (flat, BNL join) |
| 1 | SIMPLE | s | eq_ref | PRIMARY | PRIMARY | 4 | adb_network.v.source_id | 1 | |
| 1 | SIMPLE | u | eq_ref | PRIMARY | PRIMARY | 4 | adb_network.v.user_id | 1 | |
| 1 | SIMPLE | vc | ref | video_id | video_id | 4 | adb_network.v.video_id | 2 | Using index |
| 1 | SIMPLE | c | eq_ref | PRIMARY | PRIMARY | 4 | adb_network.vc.category_id | 1 | Using where |
+------+-------------+-------+--------+---------------+----------+---------+----------------------------+---------+-------------------------------------------------+
我不知道我在这里失踪了什么,首先我认为它必须与video_flags表是空的,然后我添加了一条记录,查询很快(200毫秒)但现在问题又回来了,查询将永远重新完成。
非常感谢任何帮助。
更新:为@somnium添加了没有f.reason列的explain select:
+------+-------------+-------+--------+---------------+----------+---------+----------------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+--------+---------------+----------+---------+----------------------------+------+-------------+
| 1 | SIMPLE | v | index | NULL | PRIMARY | 4 | NULL | 5 | |
| 1 | SIMPLE | f | ref | video_id | video_id | 4 | adb_network.v.video_id | 1 | Using index |
| 1 | SIMPLE | s | eq_ref | PRIMARY | PRIMARY | 4 | adb_network.v.source_id | 1 | |
| 1 | SIMPLE | u | eq_ref | PRIMARY | PRIMARY | 4 | adb_network.v.user_id | 1 | |
| 1 | SIMPLE | vc | ref | video_id | video_id | 4 | adb_network.v.video_id | 2 | Using index |
| 1 | SIMPLE | c | eq_ref | PRIMARY | PRIMARY | 4 | adb_network.vc.category_id | 1 | Using where |
+------+-------------+-------+--------+---------------+----------+---------+----------------------------+------+-------------+
解决方案:正如@somnium所建议的那样,我尝试在FORCE INDEX
列上添加video_id
,这使得查询时间从60秒缩短到272毫秒 - 仍然没有确定为什么它会在连接期间丢失索引但问题已解决。感谢
SELECT v.video_id, v.user_id, v.title, v.slug, v.rating, v.rated_by,
v.duration, v.thumb, v.total_views, v.total_comments, v.add_time,
v.view_time, v.status, v.source_id, v.orientation, v.thumbs,
v.featured, v.flagged,
u.username,
s.name,
f.reason,
GROUP_CONCAT(c.name) AS categories
FROM video v
LEFT JOIN video_flags f FORCE INDEX FOR JOIN (video_id) ON (f.video_id = v.video_id)
LEFT JOIN video_sources s ON (s.source_id = v.source_id)
LEFT JOIN user u ON (u.user_id = v.user_id)
LEFT JOIN video_category vc ON (vc.video_id = v.video_id)
LEFT JOIN video_categories c ON (c.category_id = vc.category_id) GROUP BY v.video_id ORDER BY v.video_id DESC LIMIT 10
答案 0 :(得分:2)
您不小心在大型表videos
上进行了全表扫描。可以找到潜在问题列表at the MySQL documentation。
在没有f.reason的情况下查看您的解释,优化器将忽略video_flags
表。这允许MySQL / MariaDB充分利用所有索引。
添加f.reason
时,MySQL现在需要匹配v.video_id = f.video_id
。由于video_flags
有一行,MySQL将尝试为v.video_id
中的每个条目检索video
。您似乎没有v.video_id
的索引。因此,MySQL必须从磁盘/内存中扫描完整的videos
表,才能获得video_id
。这导致检索到1219933行(相比explain select
中没有video_flags
的5)。
另一个潜在的问题是基数较低,但我不确定究竟是什么导致优化器搞砸了。
来自MySQL文档:
您正在使用基数较低的密钥(许多行与密钥值匹配) 另一栏。在这种情况下,MySQL假设可能通过使用密钥 将执行许多键查找,并且表扫描会更快。
我的理解是,由于video_flags
中的基数非常低(1-2值),可能会导致MySQL在videos
上查找由于左连接而导致的完整表(您将始终需要左侧的所有值)。此时它决定全表扫描更好。在您使用video_id
的其他情况下不会发生这种情况,因为基数较高。您可以使用FORCE INDEX
语法强制使用索引。
尝试在v.video_id
上添加索引以加快查找速度。仔细检查explain selects
以查找突然未使用的索引。
在慢速选择中,请注意表NULL
的{{1}} possible_keys
。
尝试使用v
。
希望有所帮助。
答案 1 :(得分:0)
计划A:看看这是否更好。 (似乎没有必要通过所有JOINing或GROUPing来获得你想要的10个video_ids。)
SELECT ... -- as before
FROM (
SELECT video_id
FROM video
ORDER BY video_id DESC
LIMIT 10 ) AS v1
JOIN video AS v USING (video_id)
LEFT JOIN ... -- as before
...
ORDER BY video_id DESC; -- no GROUP BY or LIMIT here
计划B:将LEFT JOIN转换为子查询
s.name,
LEFT JOIN video_sources AS s ON (s.source_id = v.source_id)
- >
( SELECT name FROM video_sources WHERE source_id = v.source_id ) AS name,
同样适用于任何其他单行值及其左连接。