【问题标题】:How to improve execution time of a Laravel Query Builder generated SQL query如何提高 Laravel Query Builder 生成的 SQL 查询的执行时间
【发布时间】:2022-01-18 23:34:38
【问题描述】:

我有三个与此查询有关的表

CREATE TABLE `tags` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `latName` varchar(191) NOT NULL,
  `araName` varchar(191) NOT NULL,
  `active` tinyint(1) NOT NULL DEFAULT 0,
  `img_name` varchar(191) DEFAULT NULL,
  `icon` varchar(191) DEFAULT NULL,
  `rgba_color` varchar(191) DEFAULT NULL,
  `color` varchar(191) DEFAULT NULL,
  `overlay` varchar(191) DEFAULT NULL,
  `position` int(11) NOT NULL,
  `mdi_icon` varchar(191) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `tags_latname_unique` (`latName`),
  UNIQUE KEY `tags_araname_unique` (`araName`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb3
 CREATE TABLE `newspapers` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `latName` varchar(191) NOT NULL,
  `araName` varchar(191) NOT NULL,
  `img_name` varchar(191) DEFAULT NULL,
  `active` tinyint(1) NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY `newspapers_latname_unique` (`latName`),
  UNIQUE KEY `newspapers_araname_unique` (`araName`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb3
CREATE TABLE `articles` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `newspaper_id` bigint(20) unsigned NOT NULL,
  `tag_id` bigint(20) unsigned NOT NULL,
  `seen` int(10) unsigned NOT NULL,
  `link` varchar(1000) NOT NULL,
  `title` varchar(191) NOT NULL,
  `img_name` varchar(191) NOT NULL,
  `date` datetime NOT NULL,
  `paragraph` text NOT NULL,
  `read_time` int(11) DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `articles_link_unique` (`link`),
  UNIQUE KEY `articles_img_name_unique` (`img_name`),
  KEY `articles_newspaper_id_foreign` (`newspaper_id`),
  KEY `articles_tag_id_foreign` (`tag_id`),
  CONSTRAINT `articles_newspaper_id_foreign` FOREIGN KEY (`newspaper_id`) REFERENCES `newspapers` (`id`),
  CONSTRAINT `articles_tag_id_foreign` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=47421 DEFAULT CHARSET=utf8mb3

基本上,我想加载具有活动报纸和活动标签的最新 5 篇文章(按日期排序)。

现在文章表包含大约 40k 条目。

这是 Laravel 的查询生成器生成的查询

SELECT `articles`.*
FROM `articles`
INNER JOIN `tags` ON `tags`.`id` = `articles`.`tag_id`
  AND `tags`.`active` = 1
INNER JOIN `newspapers` ON `newspapers`.`id` = `articles`.`newspaper_id`
  AND `newspapers`.`active` = 1
ORDER BY `date` DESC
LIMIT 5;

Mysql 运行查询大约需要 6 秒,当我删除 ORDER BY 子句时,查询变得非常快(0.001 秒)。

查询说明如下:

+------+-------------+------------+--------+-------------------------------------------------------+-------------------------------+---------+------------------------+------+----------------------------------------------+
| id   | select_type | table      | type   | possible_keys                                         | key                           | key_len | ref                    | rows | Extra                                        |
+------+-------------+------------+--------+-------------------------------------------------------+-------------------------------+---------+------------------------+------+----------------------------------------------+
|    1 | SIMPLE      | newspapers | ALL    | PRIMARY                                               | NULL                          | NULL    | NULL                   | 18   | Using where; Using temporary; Using filesort |
|    1 | SIMPLE      | articles   | ref    | articles_newspaper_id_foreign,articles_tag_id_foreign | articles_newspaper_id_foreign | 8       | mouhim.newspapers.id   | 1127 |                                              |
|    1 | SIMPLE      | tags       | eq_ref | PRIMARY                                               | PRIMARY                       | 8       | mouhim.articles.tag_id | 1    | Using where                                  |
+------+-------------+------------+--------+-------------------------------------------------------+-------------------------------+---------+------------------------+------+----------------------------------------------+

我尝试在日期属性上创建索引,但没有帮助。

为方便起见,这就是我为此查询使用查询生成器的方式:

Article::select("articles.*")
    ->join("tags", function ($join) {
        $join->on("tags.id", "articles.tag_id")
            ->where("tags.active", 1);
    })
    ->join("newspapers", function ($join) {
        $join->on("newspapers.id", "articles.newspaper_id")
            ->where("newspapers.active", 1);
    })
        ->orderBy("date", "desc")
        ->paginate(5)

起初,我使用的是 Eloquent (whereHas),但 Eloquent 使用的是 (where exists) 生成非优化查询,所以我不得不采用连接方式。

我可以做些什么来缩短这个查询的执行时间?

SHOW INDEXES FROM articles;的结果

+----------+------------+-------------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+
| Table    | Non_unique | Key_name                      | Seq_in_index | Column_name  | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Ignored |
+----------+------------+-------------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+
| articles |          0 | PRIMARY                       |            1 | id           | A         |       36072 |     NULL | NULL   |      | BTREE      |         |               | NO      |
| articles |          0 | articles_link_unique          |            1 | link         | A         |       36072 |     NULL | NULL   |      | BTREE      |         |               | NO      |
| articles |          0 | articles_img_name_unique      |            1 | img_name     | A         |       36072 |     NULL | NULL   |      | BTREE      |         |               | NO      |
| articles |          1 | articles_newspaper_id_foreign |            1 | newspaper_id | A         |          32 |     NULL | NULL   |      | BTREE      |         |               | NO      |
| articles |          1 | articles_tag_id_foreign       |            1 | tag_id       | A         |          12 |     NULL | NULL   |      | BTREE      |         |               | NO      |
| articles |          1 | data                          |            1 | date         | A         |       36072 |     NULL | NULL   |      | BTREE      |         |               | NO      |
+----------+------------+-------------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+

Rick James 建议将此查询作为解决方案

SELECT  `articles`.*
FROM  `articles`     
WHERE EXISTS ( SELECT 1 FROM tags WHERE id = `articles`.`tag_id` and active = 1)
AND EXISTS ( SELECT 1 FROM newspapers WHERE id = `articles`.`newspaper_id` and active = 1)     
ORDER BY `date` DESC
LIMIT  5;

对此查询运行 EXPLAIN 会产生以下结果

+------+-------------+------------+--------+-------------------------------------------------------+-------------------------------+---------+------------------------+------+----------------------------------------------+
| id   | select_type | table      | type   | possible_keys                                         | key                           | key_len | ref                    | rows | Extra                                        |
+------+-------------+------------+--------+-------------------------------------------------------+-------------------------------+---------+------------------------+------+----------------------------------------------+
|    1 | PRIMARY     | newspapers | ALL    | PRIMARY                                               | NULL                          | NULL    | NULL                   | 18   | Using where; Using temporary; Using filesort |
|    1 | PRIMARY     | articles   | ref    | articles_newspaper_id_foreign,articles_tag_id_foreign | articles_newspaper_id_foreign | 8       | mouhim.newspapers.id   | 1127 |                                              |
|    1 | PRIMARY     | tags       | eq_ref | PRIMARY                                               | PRIMARY                       | 8       | mouhim.articles.tag_id | 1    | Using where                                  |
+------+-------------+------------+--------+-------------------------------------------------------+-------------------------------+---------+------------------------+------+----------------------------------------------+

【问题讨论】:

  • 无论您如何优化查询,表格都会一天天增加,因此加载时间也会随之而来。您是否尝试过 yajra-datatable(服务器端)?
  • 是的,确实大小会变大,但我认为 MySQL 应该能够非常快速地处理这么多数据?
  • 对查询 (EXPLAIN SELECT...) 和 edit 将结果解释为您的问题。这可能会提供一些更有用的信息。
  • @miken32 我已经添加了EXPLAIN 的结果。谢谢!
  • 这是带有ORDER BY 子句和索引的吗?

标签: mysql laravel query-optimization


【解决方案1】:

我不确定这两个查询是否应该相同,但它们不是。

无论如何,对于第二个查询,我认为这应该更好

Article::leftJoin('tags', 'articles.tag_id', '=', 'tags.id)
   ->where('tags.latName', $tag)
   ->orderBy("articles.date", "desc")
   ->select(['articles.*'])
   ->paginate(5);

问题可能是,您在 whereIn 中创建的子查询会减慢它的速度,而 whereIn 本身也可能会减慢您的查询速度。这可以通过使用 join 和 where 来缓解。

至于第一个查询,你能说明你是如何为日期建立索引的吗? :)

【讨论】:

  • 感谢您指出这一点,我已经更正了我正在使用的 Eloquent Query 并更新了我的问题(我错误地将错误的函数复制到了我的问题中,我深表歉意...检查我的编辑回答我实际使用的查询)....至于索引,我是由CRETE INDEX date_index ON articles(date);创建的
  • 一篇文章可以有多个“活动标签”吗?如果是这样,我认为你的查询会给你不想要的重复。
  • 嘿@RickJames,不,永远不会有重复,因为两者之间的关系是一对多;因此每篇文章都有一个且只有一个标签与之关联。
【解决方案2】:

假设你不想重复,改成这个;它可能会更快:

SELECT  `articles`.*
    FROM  `articles`
    WHERE EXISTS ( SELECT 1 FROM tags
                        WHERE id = `articles`.`tag_id` )
      AND EXISTS ( SELECT 1 FROM newspapers
                        WHERE id = `articles`.`newspaper_id` )
    ORDER BY  `date` DESC
    LIMIT  5;

另外,在articles 上有这个索引:

INDEX(date)

(这是一个罕见的用例,开始索引的列将在“范围”中使用。)

(抱歉,我不会说“Laravel”;也许其他人可以在这方面提供帮助。)

PS。在一个表上拥有 3 个 UNIQUE 键是非常不寻常的。这通常表明架构设计存在问题。

每篇文章都有一个且只有一个与之关联的标签

多篇文章可以有相同的标签吗?

当我删除 ORDER BY 子句时,查询变得非常快(0.001 秒)。

那是因为你得到任何 5 行很容易返回给你。显然,ORDER BY 是要求的一部分。 “使用临时;使用文件排序”说至少有一种排序。它实际上是一个“文件”排序——因为SELECT * 包含一个TEXT 列。 (有一种技术可以避免“文件”,但我认为这里不需要。)

【讨论】:

  • 不用担心 Laravel,此时我只想了解和改进查询(我可以稍后将其翻译成 Laravel)。我已经在date 上建立了索引:CREATE INDEX date ON articles(date); ... 3 个表之间的关系如下:每篇文章有一个且只有一个标签,并且只有一个报纸。我想检索同时具有活动标签和报纸的文章(并且只获取最后 5 个)
  • 是的,多篇文章可以有相同的标签和同一份报纸......有9个标签和30份报纸,有40k篇文章。
  • @HamzaMogni - 我又添加了一些。
  • 40K 文章中有多少是“活跃的”? (如果大多数都处于非活动状态,我建议的查询可能存在性能问题。)
  • @HamzaMogni - 哦,好吧,优化器足够聪明,可以将我的配方变成你的!我不喜欢使用“索引提示”,但FORCE INDEX('data') 可能是解决方案。
猜你喜欢
  • 1970-01-01
  • 2019-04-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-06
  • 2014-04-10
  • 1970-01-01
  • 2021-02-04
相关资源
最近更新 更多