用 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 模型的所有字符串属性