php - 特定表上的 LEFT JOIN 速度极慢

标签 php mysql mariadb ubuntu-16.04

我无法找出查询速度极慢的原因;在配备 48GB DDR3 的 Dual Xeon L5630 上运行 60 秒,运行 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 表中,因为当我注释 f.reason 字段和 video_flags 上的左连接时,查询只需要 152 毫秒。 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 表为空有关,然后我添加了一条记录并且查询很快(200ms),但现在问题又回来了,查询需要很长时间才能再次完成。

非常感谢任何帮助。


更新:为 @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

最佳答案

您不小心对大型表视频进行了全表扫描。可以找到潜在问题的列表 at the MySQL documentation .

潜在问题

缺少键

在没有 f.reason 的情况下查看您的解释,优化器将忽略 video_flags 表。这使得 MySQL/MariaDB 能够充分利用所有索引。

添加f.reason时,MySQL现在需要匹配v.video_id = f.video_id。由于video_flags只有一行,MySQL将尝试检索video中每个条目的v.video_id。您似乎没有 v.video_id 索引。因此MySQL必须从磁盘/内存中扫描完整的videos表才能获取video_id。这会导致检索 1219933 行(相比之下,在没有 video_flagsexplain select 中检索 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 上的完整表加入(您始终需要左侧的所有值)。此时它决定全表扫描更好。在您使用 video_id 的其他情况下,不会发生这种情况,因为基数更高。您可以使用 FORCE INDEX 语法强制使用索引。

潜在的解决方案

尝试在 v.video_id 上添加索引以加快查找速度。仔细检查两个 explain selects 以查找哪些索引突然不再使用。 请注意慢速选择中表 vpossible_keysNULL

尝试使用FORCE INDEX

希望有帮助。

关于php - 特定表上的 LEFT JOIN 速度极慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39745267/

相关文章:

php - 如何使用 MSSQL 配置 XAMPP v. 3.2.2 (PHP 7.2)?

php - curl 错误 60 : SSL Certificate unable to get local issuer certificate:Tried Everything

mysql - Centos 7 , WHM 高 Mysql&MariaDB CPU 使用率 %600

mysql - 在已安装Mysql的Centos6.9上安装MariaDB时发生冲突

mysql - 使用 maria DB、JPA 和 Spring Boot 1.5.4 基于架构的 Multi-Tenancy

php - 来自两个表的 SQL 查询按两个表列排序结果

php - 为什么此 SQL 会出现语法错误?

php - 带有 PDO 的混合 UTF-8 和 latin1 表

mysql - SQL-DBDL 如何约束父类(super class)参与子类?

具有多对多关系的 MySQL SELECT 查询