特定 table 上的 LEFT JOIN 极慢
Extremely slow LEFT JOIN on specific table
我无法找出查询速度极慢的原因; Dual Xeon L5630 with 48GB DDR3 运行ning Ubuntu 16.04 with PHP7.0-FPM 和 MariaDB 10.0.27
60 秒
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 table,因为当我评论 f.reason 字段和左边加入 video_flags,查询仅需 152 毫秒。 video_flags table 在 video_id 上有一个索引并且字段类型在两个 tables INT(11)
中是相同的
当我运行解释select时,我得到以下回复:
+------+-------------+-------+--------+---------------+----------+---------+----------------------------+---------+-------------------------------------------------+
| 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 table 为空的东西,然后我添加了一条记录并且查询很快( 200 毫秒),但现在问题又回来了,查询又要花很长时间才能再次完成。
非常感谢任何帮助。
更新: 添加了解释 select 而没有 @somnium 的 f.reason 列:
+------+-------------+-------+--------+---------------+----------+---------+----------------------------+------+-------------+
| 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 所建议的,我尝试在 video_id
列上添加一个 FORCE INDEX
,这使查询时间从 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
您不小心导致了对大型 table videos
的完整 table 扫描。可以找到潜在问题的列表 at the MySQL documentation。
潜在问题
缺少钥匙
查看您没有 f.reason 的解释,优化器将忽略 video_flags
table。这允许 MySQL/MariaDB 充分利用所有索引。
添加f.reason
时,MySQL现在需要匹配v.video_id = f.video_id
。由于 video_flags
有一行,MySQL 将尝试为 video
中的每个条目检索 v.video_id
。您似乎没有关于 v.video_id
的索引。因此 MySQL 必须从 disk/memory 扫描完整的 videos
table 以获得 video_id
。这导致检索到 1219933 行(与 explain select
中没有 video_flags
的 5 行相比)。
低基数
另一个潜在的问题是低基数,但我不确定到底是什么导致优化器搞砸了。
来自 MySQL 文档:
You are using a key with low cardinality (many rows match the key value) through
another column. In this case, MySQL assumes that by using the key it probably
will do many key lookups and that a table scan would be faster.
我的理解是,由于 video_flags
中的基数非常低(1-2 个值),它可能会导致 MySQL 在 videos
上查找完整的 table由于 Left Join(您将始终需要左侧的所有值)。此时它决定完整 table 扫描更好。在您使用 video_id
的其他情况下不会发生这种情况,因为基数更高。您可以使用 FORCE INDEX
语法强制使用索引。
可能的解决方案
尝试在 v.video_id
上添加索引以加快查找速度。仔细检查 explain selects
以找出哪些索引突然没有被使用。
注意 NULL
for possible_keys
for table v
in your slow select.
尝试使用 FORCE INDEX
。
希望对您有所帮助。
方案 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,
任何其他单行值及其左连接也是如此。
我无法找出查询速度极慢的原因; Dual Xeon L5630 with 48GB DDR3 运行ning Ubuntu 16.04 with PHP7.0-FPM 和 MariaDB 10.0.27
60 秒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 table,因为当我评论 f.reason 字段和左边加入 video_flags,查询仅需 152 毫秒。 video_flags table 在 video_id 上有一个索引并且字段类型在两个 tables INT(11)
中是相同的当我运行解释select时,我得到以下回复:
+------+-------------+-------+--------+---------------+----------+---------+----------------------------+---------+-------------------------------------------------+
| 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 table 为空的东西,然后我添加了一条记录并且查询很快( 200 毫秒),但现在问题又回来了,查询又要花很长时间才能再次完成。
非常感谢任何帮助。
更新: 添加了解释 select 而没有 @somnium 的 f.reason 列:
+------+-------------+-------+--------+---------------+----------+---------+----------------------------+------+-------------+
| 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 所建议的,我尝试在 video_id
列上添加一个 FORCE INDEX
,这使查询时间从 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
您不小心导致了对大型 table videos
的完整 table 扫描。可以找到潜在问题的列表 at the MySQL documentation。
潜在问题
缺少钥匙
查看您没有 f.reason 的解释,优化器将忽略 video_flags
table。这允许 MySQL/MariaDB 充分利用所有索引。
添加f.reason
时,MySQL现在需要匹配v.video_id = f.video_id
。由于 video_flags
有一行,MySQL 将尝试为 video
中的每个条目检索 v.video_id
。您似乎没有关于 v.video_id
的索引。因此 MySQL 必须从 disk/memory 扫描完整的 videos
table 以获得 video_id
。这导致检索到 1219933 行(与 explain select
中没有 video_flags
的 5 行相比)。
低基数
另一个潜在的问题是低基数,但我不确定到底是什么导致优化器搞砸了。
来自 MySQL 文档:
You are using a key with low cardinality (many rows match the key value) through another column. In this case, MySQL assumes that by using the key it probably will do many key lookups and that a table scan would be faster.
我的理解是,由于 video_flags
中的基数非常低(1-2 个值),它可能会导致 MySQL 在 videos
上查找完整的 table由于 Left Join(您将始终需要左侧的所有值)。此时它决定完整 table 扫描更好。在您使用 video_id
的其他情况下不会发生这种情况,因为基数更高。您可以使用 FORCE INDEX
语法强制使用索引。
可能的解决方案
尝试在 v.video_id
上添加索引以加快查找速度。仔细检查 explain selects
以找出哪些索引突然没有被使用。
注意 NULL
for possible_keys
for table v
in your slow select.
尝试使用 FORCE INDEX
。
希望对您有所帮助。
方案 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,
任何其他单行值及其左连接也是如此。