从Laravel 5.1升级到Laravel 5.8后,where(Has())变慢

时间:2019-07-27 09:47:34

标签: mysql laravel eloquent mariadb laravel-5.8

我通过建立一个新的5.8项目并复制文件并在此处和此处进行一些调整,将应用程序从Laravel 5.1切换到Laravel 5.8。

问题在于,whereHas的查询变得非常慢。

这是示例代码:

Article::whereHas('categories', function ($category) {
            $category->where('link', 'foto');
        })
        ->active()
        ->recent()
        ->take(3)
        ->get();

此代码在Laravel 5.1上生成以下查询,并在0.05-0.07秒内完成。

SELECT *
FROM `articles`
WHERE `articles`.`deleted_at` IS NULL
  AND
    (SELECT count(*)
     FROM `categories`
     INNER JOIN `article_category` 
       ON `categories`.`id` = `article_category`.`category_id`
     WHERE `article_category`.`article_id` = `articles`.`id`
       AND `link` = 'foto'
       AND `categories`.`deleted_at` IS NULL) >= 1
ORDER BY IFNULL(published_at, created_at) DESC
LIMIT 3

这是解释:

+------+--------------------+------------------+------+--------------------------------------------------------------------------+-------------------------------------+---------+-----------------+------+----------+------------------------------------+
| id   | select_type        | table            | type | possible_keys                                                            | key                                 | key_len | ref             | rows | filtered | Extra                              |
+------+--------------------+------------------+------+--------------------------------------------------------------------------+-------------------------------------+---------+-----------------+------+----------+------------------------------------+
|    1 | PRIMARY            | articles         | ALL  | NULL                                                                     | NULL                                | NULL    | NULL            | 4846 |   100.00 | Using where; Using filesort        |
|    2 | DEPENDENT SUBQUERY | categories       | ref  | PRIMARY,categories_link_index                                            | categories_link_index               | 767     | const           |    1 |   100.00 | Using index condition; Using where |
|    2 | DEPENDENT SUBQUERY | article_category | ref  | article_category_category_id_foreign,article_category_article_id_foreign | article_category_article_id_foreign | 4       | lcf.articles.id |    1 |   100.00 | Using where                        |
+------+--------------------+------------------+------+--------------------------------------------------------------------------+-------------------------------------+---------+-----------------+------+----------+------------------------------------+

在Laravel 5.8上,它将生成以下查询,运行10-13秒。

SELECT *
FROM `articles`
WHERE EXISTS
    (SELECT *
     FROM `categories`
     INNER JOIN `article_category` 
       ON `categories`.`id` = `article_category`.`category_id`
     WHERE `articles`.`id` = `article_category`.`article_id`
       AND `link` = 'foto'
       AND `categories`.`deleted_at` IS NULL)
  AND `articles`.`deleted_at` IS NULL
ORDER BY IFNULL(published_at, created_at) DESC
LIMIT 3

这是解释

+------+--------------+------------------+------+--------------------------------------------------------------------------+--------------------------------------+---------+-------------------+------+----------+------------------------------------+
| id   | select_type  | table            | type | possible_keys                                                            | key                                  | key_len | ref               | rows | filtered | Extra                              |
+------+--------------+------------------+------+--------------------------------------------------------------------------+--------------------------------------+---------+-------------------+------+----------+------------------------------------+
|    1 | PRIMARY      | <subquery2>      | ALL  | distinct_key                                                             | NULL                                 | NULL    | NULL              |  107 |   100.00 | Using temporary; Using filesort    |
|    1 | PRIMARY      | articles         | ALL  | PRIMARY                                                                  | NULL                                 | NULL    | NULL              | 4846 |    75.01 | Using where                        |
|    2 | MATERIALIZED | categories       | ref  | PRIMARY,categories_link_index                                            | categories_link_index                | 767     | const             |    1 |   100.00 | Using index condition; Using where |
|    2 | MATERIALIZED | article_category | ref  | article_category_category_id_foreign,article_category_article_id_foreign | article_category_category_id_foreign | 4       | lcf.categories.id |  107 |   100.00 |                                    |
+------+--------------+------------------+------+--------------------------------------------------------------------------+--------------------------------------+---------+-------------------+------+----------+------------------------------------+

我在相同的服务器,相同的MariaDB 10.2.24数据库上运行了两个代码库。数据集中的数据约有6k条,80个类别和10k条记录。

我应该在这里做什么?到目前为止,我在代码库中发现了10多个受此问题困扰的查询。我可以以某种方式翻转配置中的开关并使用旧方法使它们全部检查是否存在吗?还是应该以某种方式指导每个查询以改进其计划?

更新

我刚刚注意到,如果使用whereHas(..., '>', 0),我可以获得几乎与旧性能相同的旧查询(实际上是WHERE (SELECT COUNT...) > 0)。但是,whereHas(..., '>=', 1)确实减少了自己使用EXISTS进行查询的时间。仍然存在一个问题,我是否可以在不编辑每个查询的情况下切换整个应用程序的行为。

评论的答案

索引其他文章

+----------+------------+----------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table    | Non_unique | Key_name                   | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------+------------+----------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| articles |          0 | PRIMARY                    |            1 | id          | A         |        4846 |     NULL | NULL   |      | BTREE      |         |               |
| articles |          1 | articles_author_id_foreign |            1 | author_id   | A         |          18 |     NULL | NULL   | YES  | BTREE      |         |               |
+----------+------------+----------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

article_category上的索引

+------------------+------------+--------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table            | Non_unique | Key_name                             | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+------------------+------------+--------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| article_category |          0 | PRIMARY                              |            1 | id          | A         |        9676 |     NULL | NULL   |      | BTREE      |         |               |
| article_category |          1 | article_category_category_id_foreign |            1 | category_id | A         |          90 |     NULL | NULL   |      | BTREE      |         |               |
| article_category |          1 | article_category_article_id_foreign  |            1 | article_id  | A         |        9676 |     NULL | NULL   |      | BTREE      |         |               |
+------------------+------------+--------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

运行示例的数据可以在这里找到:https://gist.github.com/tontonsb/b97bc33066a67e9d8bc3654f2c01c103

运行速度更快,但是仍然是2.8 vs 0.07秒,因此至少在MariaDB 10.2.24上可以清楚地看到问题。可能速度有所提高,因为我删除了其他列及其索引。

1 个答案:

答案 0 :(得分:0)

尝试一下:

$articles = Article::query()
    ->hasByNonDependentSubquery('categories', function ($category) {
        $category->where('link', 'foto');
    })
    ->active()
    ->recent()
    ->take(3)
    ->get();