用 Laravel/Eloquent 为 Yajra DataTables 编写连接查询的慢 MySQL

Posted

技术标签:

【中文标题】用 Laravel/Eloquent 为 Yajra DataTables 编写连接查询的慢 MySQL【英文标题】:Slow MySQL with join query written in Laravel/Eloquent for Yajra DataTables 【发布时间】:2021-06-20 06:06:53 【问题描述】:

我试图通过加入数据从两个表中提取数据,但是执行的 sql 查询非常慢。想法是提取所有users,然后与points 表中的created_at 日期结合。拉出所有用户或所有点相当快,但在编写正确的连接 sql 时会遇到问题。我确实尝试将索引添加到适当的列(例如,points.created_at),但那些使查询变得更慢。

这是生成查询的代码:

return $this->user
            ->query()
            ->select(['users.id', 'users.email', 'users.role', 'users.created_at', 'users.updated_at', 'pt.created_at AS last_transaction'])
            ->leftJoin(DB::raw('(SELECT points.user_id, points.created_at FROM points ORDER BY points.created_at DESC) AS pt'), 'pt.user_id', '=', 'users.id')
            ->where('users.role', 'consument')
            ->groupBy('users.id');

生成此查询:

select `users`.`id`, `users`.`email`, `users`.`role`, `users`.`created_at`, `users`.`updated_at`, `pt`.`created_at` as `last_transaction`
from `users`
left join (SELECT points.user_id, points.created_at FROM points ORDER BY points.created_at DESC) AS pt on `pt`.`user_id` = `users`.`id`
where `users`.`role` = ? and `users`.`deleted_at` is null
group by `users`.`id`
order by `id` asc

用户表:

CREATE TABLE `users` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `email` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `password` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `remember_token` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
  `role` varchar(15) COLLATE utf8_unicode_ci DEFAULT 'consument',
  `created_at` timestamp NOT NULL DEFAULT current_timestamp(),
  `updated_at` timestamp NOT NULL DEFAULT current_timestamp(),
  `deleted_at` timestamp NULL DEFAULT NULL,
  `email_verified_at` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `email_verify_token` text COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `users_email_unique` (`email`)
) ENGINE=InnoDB AUTO_INCREMENT=84345 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

积分表:

CREATE TABLE `points` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int(10) unsigned NOT NULL,
  `tablet_id` int(10) unsigned DEFAULT NULL,
  `parent_company` int(10) unsigned NOT NULL,
  `company_id` int(10) unsigned NOT NULL,
  `points` int(10) unsigned NOT NULL,
  `mutation_type` tinyint(3) unsigned NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT current_timestamp(),
  `updated_at` timestamp NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `points_user_id_foreign` (`user_id`),
  KEY `points_company_id_foreign` (`company_id`),
  KEY `points_parent_company_index` (`parent_company`),
  KEY `points_tablet_id_index` (`tablet_id`),
  KEY `points_mutation_type_company_id_created_at_index` (`mutation_type`,`company_id`,`created_at`),
  KEY `created_at_user_id` (`created_at`,`user_id`),
  CONSTRAINT `points_company_id_foreign` FOREIGN KEY (`company_id`) REFERENCES `companies` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `points_parent_company_foreign` FOREIGN KEY (`parent_company`) REFERENCES `parent_company` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `points_tablet_id_foreign` FOREIGN KEY (`tablet_id`) REFERENCES `tablets` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
  CONSTRAINT `points_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1798627 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

用户有 84,263 行,点数有 1,636,119 行。如果我通过 phpMyAdmin 手动执行查询,执行大约需要 150 秒。如果我通过 Laravel 运行它,页面会在 180 秒后超时。

我可以添加或删除索引并更改 sql 查询,但我无法更改数据库结构,因此非常感谢任何有关优化 sql 查询的帮助。

【问题讨论】:

【参考方案1】:

如果积分表中每个用户只有一行,请执行以下操作:

如果有用户没有帖子,您可以使用:

select `users`.`id`, `users`.`email`, `users`.`role`, `users`.`created_at`, 
`users`.`updated_at`, `pt`.`created_at` as `last_transaction`
from `users`
left join points AS pt on `pt`.`user_id` = `users`.`id`
where `users`.`role` = ? and `users`.`deleted_at` is null
order by `id` ASC

如果用户表中的每个用户在积分表中始终有一个帖子,您可以跳过左连接,只需:

select `users`.`id`, `users`.`email`, `users`.`role`, `users`.`created_at`, 
`users`.`updated_at`, `pt`.`created_at` as `last_transaction`
from `users`
join points AS pt on `pt`.`user_id` = `users`.`id`
where `users`.`role` = ? and `users`.`deleted_at` is null
order by `id` asc

【讨论】:

我明白我不够清楚,对此感到抱歉。每个用户在points 表中可以有多行,这就是为什么有这么多行。我需要在每个用户的points 表中获取最新的进入日期,同时列出所有用户。 好的。我会做出新的回答。【参考方案2】:

这将仅返回一个用户和基于点列“created_at”的最新点行。

SELECT  `u`.`id`, 
        `u`.`email`, 
        `u`.`role`, 
        `u`.`created_at`,
        `u`.`updated_at`, 
        `pt`.`created_at` as `last_transaction`
from `users` u
LEFT join points AS pt on `pt`.`user_id` = `u`.`id`
LEFT JOIN (
    SELECT user_id, MAX(created_at) AS mm FROM points GROUP BY user_id
) AS m ON m.user_id = pt.user_id
where `u`.`role` = ? and `u`.`deleted_at` is NULL AND m.mm = pt.created_at
order by `id` ASC;

【讨论】:

以上是关于用 Laravel/Eloquent 为 Yajra DataTables 编写连接查询的慢 MySQL的主要内容,如果未能解决你的问题,请参考以下文章

一次更新多行 Laravel Eloquent

修剪 Laravel 中所有 Eloquent 模型的所有字符串属性

Laravel Eloquent模型单元测试

Laravel Eloquent 模型单元测试

Laravel Eloquent 验证请求的 items 字段 foreach

将 MariaDB 语句转换为 Laravel4 Eloquent-Query