Laravel - 如何优化 MIN - MAX - orderBy 查询?

Posted

技术标签:

【中文标题】Laravel - 如何优化 MIN - MAX - orderBy 查询?【英文标题】:Laravel - How to optimize MIN - MAX - orderBy queries? 【发布时间】:2020-06-28 12:52:53 【问题描述】:

我在 Laravel 中的代码是:

Car::selectRaw('*,
    MIN(car_prices.price) AS min_price,
    MAX(car_prices.price) AS max_price,
    MAX(car_prices.updated_at) AS latest_update')
->leftJoin('car_prices', 'car_prices.car_id', 'cars.id')
->groupBy('car_prices.car_id')
->orderBy('latest_update', 'desc')
->paginate(10);

需要很长时间才能运行到抛出错误:

最长执行时间超过 60 秒

cars 表中的记录数为 100,000 和 car_prices 中的 6,000,000。

表格结构:

CREATE TABLE `cars` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=110001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

CREATE TABLE `car_prices` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `car_id` bigint(20) unsigned NOT NULL,
  `price` decimal(8,2) NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `car_prices_car_id_foreign` (`car_id`)
) ENGINE=MyISAM AUTO_INCREMENT=5506827 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

查询:

select count(*) as aggregate
    from `cars`
    left join `car_prices`
    on `car_prices`.`car_id` = `cars`.`id`
    group by `car_prices`.`car_id`;

select *,
    MIN(car_prices.price) AS min_price,
    MAX(car_prices.price) AS max_price,
    MAX(car_prices.updated_at) AS latest_update from `cars`
    left join `car_prices`
    on `car_prices`.`car_id` = `cars`.`id`
    group by `car_prices`.`car_id`
    order by `latest_update` desc
    limit 10
    offset 0;

如何优化它?我应该缓存数据吗?还是有比这更好的查询?

我的硬盘是SSD innodb_flush_log_at_trx_commit 的值 = 1 从上午 10 点到下午 2 点,写入/插入的数量大约为 1000/秒,在此期间之前和之后的请求要少得多。

【问题讨论】:

这个语句是否必要 (->orderBy('latest_update', 'desc'))? 你可以尝试索引 当您询问有关查询优化的问题时,请始终将SHOW CREATE TABLE <tablename> 的结果包含在查询中涉及的表中。不要让我们猜测您拥有的数据类型、索引和约束。帮助我们为您提供帮助! 请提供生成的SQL。我们中的一些人在 SQL 中比在 许多 个前端包中更容易发现这些问题。 latest_update 在哪个表中?它在优化方面产生了很大的不同。 【参考方案1】:

你需要在查询中拥有更好的汽车表唯一索引 latest_update 或删除 ->orderBy('latest_update', 'desc')。并在收到结果后进行排序

你可以用explain查看mysql的性能

EXPLAIN SELECT * FROM car order by latest_update desc;

/// 检查这个https://www.exoscale.com/syslog/explaining-mysql-queries/#:~:text=the%20last%20decade.-,Explain,DELETE%20%2C%20REPLACE%20%2C%20and%20UPDATE%20。

和https://dev.mysql.com/doc/refman/5.7/en/using-explain.html#:~:text=The%20EXPLAIN%20statement%20provides%20information,%2C%20REPLACE%20%2C%20and%20UPDATE%20statements.&text=That%20is%2C%20MySQL%20explains%20how,joined%20and%20in%20which%20order。

基本上你需要优化(更好的索引)你的数据库表“汽车”,使其表现良好

还有其他你可能会尝试增加执行时间的事情 在 php.ini 中,您需要设置 max_execution_time = 600 或更多内容,以检查完成执行所需的时间。 https://www.codewall.co.uk/increase-php-script-max-execution-time-limit-using-ini_set-function/

【讨论】:

由于LIMIT,在服务器中排序效率更高。否则,必须将无数行从服务器铲到客户端。 这个,单独会使查询更慢。【参考方案2】:

您使用的查询不适用于如此大的表。而是每当进入表 car_prices 的条目设置操作并获取最小值和最大值并将其存储在汽车表中。或者你可以为此设置一个crone。

【讨论】:

【参考方案3】:

在这两个查询中,

GROUP BY cars.id

这不是使用car_prices.car_id,因为LEFT JOIN 可能会丢失。

一旦你这样做了,第一个查询(只有COUNT)可以删除JOIN。然后GROUP BY 就变得多余了:

select  count(*) as aggregate
    from  `cars`

第二个查询有问题。

使用当前设计,您必须遍历所有两个表。呃。

另外...如果给定的汽车没有价格,它将有 NULL 对应于 latest_update,因此它将在 100,000 行的末尾进行排序。鉴于此,您最好不要展示这些汽车;这将简化查询以进行更好的优化。

如果您需要列出没有价格的汽车,请在 UI 中单独提出请求。该查询将是 LEFT JOIN .. IS NULL,不需要 MAX()s

但是,我仍然担心用户需要分页的 10,000 个页面。

从 MyISAM 切换到 InnoDB。

如果您没有将它们用于任何用途,请扔掉 created_atupdated_at

之后,cars 只是idname 之间的映射。这可能可以让您避免通过cars。而是做类似的事情

SELECT  ( SELECT name FROM cars WHERE id = x.car_id ) AS name, 
        ...
     FROM ...

另一个想法是,每当您向car_prices 添加一行时,您都会在cars 中更新updated_at。这将允许您完全在cars 中找到这 10 辆汽车。

决定你愿意牺牲什么。

更多

注意:使用 MyISAM,慢速 SELECT 会阻塞 UPDATE。使用 InnoDB,可以并行运行; SELECT 使用 UPDATE 之前的值。无论哪种方式,选择都在某个“时间点”。但是 InnoDB 允许更多的并行性。

这是一个权衡。更新的小幅放缓以实现选择的大幅加速。 (不,我不确定我的建议是否“更快”)

一些进一步的问题来分析权衡:

磁盘:HDD 还是 SSD? innodb_flush_log_at_trx_commit 的值(更改为 InnoDB 后)。 多少流量?首先,写入(插入/删除)的次数是否超过 100/秒?

【讨论】:

谢谢@RickJames,但现在还有另一个问题。在最后一段中,您说过要为价格更新日期添加一列,以免使用car_prices 表对其进行排序。因为大约有 200 个在线用户,他们每个人每 15 分钟更新和插入大约 1000 种产品的价格。这些更新和插入将比选择花费更多时间。我现在该怎么办? @kodfire - 是的。见我的补充。 已编辑详细信息。 @RickJames @kodfire - 每秒 1000 次插入来自程序,而不是人类?如果是这样,它们是分批的。示例:您从一个来源读取 4K 数据点,需要几秒钟或几分钟才能将它们铲入数据库?与此同时,另外 7K 条记录来自另一个来源。等等。如果是这样,它们可以组合成一个INSERT,性能会好很多。 (我无法想象人类输入的 1400 万条记录!) 让我告诉你这个场景。用户想要更改价格的汽车有 1000 辆。所以她/他将编辑她/他以前对这些特定汽车的价格。我可以通过将相同的价格收集到一个数组中并更新所有用户想要将它们更改为 15900 的汽车来使其变得更好(例如),但在最坏的情况下,用户再次设置了不同的价格并且它们都不相同。在最坏的情况下,每个用户最多为她/他的汽车更新 1000 个价格,并且在那一秒可能会获得 200 个用户。所以 1000 * 200 = 200000 个请求!!! @RickJames

以上是关于Laravel - 如何优化 MIN - MAX - orderBy 查询?的主要内容,如果未能解决你的问题,请参考以下文章

使用 where min/max laravel 获取所有行

如何在laravel中进行多字段唯一验证?

min/max优化,count ,group by

Mysql 优化 MIN、MAX 和 SUM 的索引开销

优化时间序列数据的 MIN / MAX 查询

是否可以微优化“x = max(a,b); y = min(a,b);”?