特定表上的LEFT JOIN非常慢

时间:2016-09-28 10:46:00

标签: php mysql mariadb ubuntu-16.04

我无法找出为什么我的查询非常慢;双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

2 个答案:

答案 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,

同样适用于任何其他单行值及其左连接。