gpt4 book ai didi

mysql - 有什么方法可以优化此MySQL查询吗? (资源密集)

转载 作者:行者123 更新时间:2023-11-29 02:17:13 25 4
gpt4 key购买 nike

我的应用程序需要经常运行此查询,该查询获取应用程序要显示的用户数据列表。问题是关于user_quiz的子查询资源很重,计算排名也非常耗费CPU。
基准:每次运行约5秒
何时运行:
当用户想要查看他们的排名时
当用户想要查看其他人的排名时
获取用户朋友列表
.5秒考虑到此查询将经常运行,这是一段很长的时间。我能做些什么来优化这个查询吗?
user表:

CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`firstname` varchar(100) DEFAULT NULL,
`lastname` varchar(100) DEFAULT NULL,
`password` varchar(20) NOT NULL,
`email` varchar(300) NOT NULL,
`verified` tinyint(10) DEFAULT NULL,
`avatar` varchar(300) DEFAULT NULL,
`points_total` int(11) unsigned NOT NULL DEFAULT '0',
`points_today` int(11) unsigned NOT NULL DEFAULT '0',
`number_correctanswer` int(11) unsigned NOT NULL DEFAULT '0',
`number_watchedvideo` int(11) unsigned NOT NULL DEFAULT '0',
`create_time` datetime NOT NULL,
`type` tinyint(1) unsigned NOT NULL DEFAULT '1',
`number_win` int(11) unsigned NOT NULL DEFAULT '0',
`number_lost` int(11) unsigned NOT NULL DEFAULT '0',
`number_tie` int(11) unsigned NOT NULL DEFAULT '0',
`level` int(1) unsigned NOT NULL DEFAULT '0',
`islogined` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=230 DEFAULT CHARSET=utf8;

user_quiz表:
CREATE TABLE `user_quiz` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`question_id` int(11) NOT NULL,
`is_answercorrect` int(11) unsigned NOT NULL DEFAULT '0',
`question_answer_datetime` datetime NOT NULL,
`score` int(1) DEFAULT NULL,
`quarter` int(1) DEFAULT NULL,
`game_type` int(1) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=9816 DEFAULT CHARSET=utf8;

user_starter表:
CREATE TABLE `user_starter` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`result` int(1) DEFAULT NULL,
`created_date` date DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=456 DEFAULT CHARSET=utf8mb4;

我的索引:
Table: user
Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
user 0 PRIMARY 1 id A 32 BTREE

Table: user_quiz
Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
user_quiz 0 PRIMARY 1 id A 9462 BTREE
user_quiz 1 user_id 1 user_id A 270 BTREE

Table: user_starter
Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
user_starter 0 PRIMARY 1 id A 454 BTREE
user_starter 1 user_id 1 user_id A 227 YES BTREE

查询:
SET @curRank = 0;
SET @lastPlayerPoints = 0;
SELECT
sub.*,
@curRank := IF(@lastPlayerPoints!=points_week, @curRank + 1, @curRank) AS rank,
@lastPlayerPoints := points_week AS db_PPW
FROM (
SELECT u.id,u.firstname,u.lastname,u.email,u.avatar,u.type,u.points_total,u.number_win,u.number_lost,u.number_tie,u.verified,
COALESCE(SUM(uq.score),0) as points_week,
COALESCE(us.number_lost,0) as number_week_lost,
COALESCE(us.number_win,0) as number_week_win,
(select MAX(question_answer_datetime) from user_quiz WHERE user_id = u.id and game_type = 1) as lastFrdFight,
(select MAX(question_answer_datetime) from user_quiz WHERE user_id = u.id and game_type = 2) as lastBotFight
FROM `user` u
LEFT JOIN (SELECT user_id,
count(case when result=1 then 1 else null end) as number_win,
count(case when result=-1 then 1 else null end) as number_lost
from user_starter where created_date BETWEEN '2016-01-11 00:00:00' AND '2016-05-12 05:10:27' ) us ON u.id = us.user_id
LEFT JOIN (SELECT * FROM user_quiz WHERE question_answer_datetime BETWEEN '2016-01-11 00:00:00' AND '2016-05-12 00:00:00') uq on u.id = uq.user_id
GROUP BY u.id ORDER BY points_week DESC, u.lastname ASC, u.firstname ASC
) as sub

说明:
id    select_type    table    type    possible_keys    key    key_len    ref    rows    filtered    Extra
1 PRIMARY <derived2> ALL 3027 100
2 DERIVED u ALL PRIMARY 32 100 Using temporary; Using filesort
2 DERIVED <derived5> ALL 1 100 Using where; Using join buffer (Block Nested Loop)
2 DERIVED <derived6> ref <auto_key0> <auto_key0> 4 fancard.u.id 94 100
6 DERIVED user_quiz ALL 9461 100 Using where
5 DERIVED user_starter ALL 454 100 Using where
4 DEPENDENT SUBQUERY user_quiz ref user_id user_id 4 func 35 100 Using where
3 DEPENDENT SUBQUERY user_quiz ref user_id user_id 4 func 35 100 Using where

示例输出和预期输出:
enter image description here
基准点:大约0.5秒

最佳答案

下面的索引应该使子查询变得超快速。

ALTER TABLE user_quiz
ADD INDEX (`user_id`,`game_type`,`question_answer_datetime`)

请为所有表提供 user_quiz语句,因为这将有助于进行其他优化。
更新#1
好吧,我花了一些时间来研究一下,幸运的是,在优化方面,似乎有很多相对较低的挂果。
以下是要添加的所有索引:
ALTER TABLE user_quiz
ADD INDEX `userGametypeAnswerDatetimes` (`user_id`,`game_type`,`question_answer_datetime`)

ALTER TABLE user_quiz
ADD INDEX `userAnswerScores` (`user_id`,`question_answer_datetime`,`score`)

ALTER TABLE user_starter
ADD INDEX `userResultDates` (`user_id`,`result`,`created_date`)

请注意,名称(如 SHOW CREATE TABLE tablename)是可选的,您可以将它们命名为对您最有意义的名称。但是,一般来说,将特定的名称放在自定义索引上是很好的(只是为了组织的目的)
现在,您的查询应该可以处理这些新索引:
SET @curRank = 0;
SET @lastPlayerPoints = 0;
SELECT
sub.*,
@curRank := IF(@lastPlayerPoints!=points_week, @curRank + 1, @curRank) AS rank,
@lastPlayerPoints := points_week AS db_PPW
FROM (
SELECT u.id,
u.firstname,
u.lastname,
u.email,
u.avatar,
u.type,
u.points_total,
u.number_win,
u.number_lost,
u.number_tie,
u.verified,
COALESCE(user_scores.score,0) as points_week,
COALESCE(user_losses.number_lost,0) as number_week_lost,
COALESCE(user_wins.number_win,0) as number_week_win,
(
select MAX(question_answer_datetime)
from user_quiz
WHERE user_id = u.id and game_type = 1
) as lastFrdFight,
(
select MAX(question_answer_datetime)
from user_quiz
WHERE user_id = u.id
and game_type = 2
) as lastBotFight
FROM `user` u
LEFT OUTER JOIN (
SELECT user_id,
COUNT(*) AS number_won
from user_starter
WHERE created_date BETWEEN '2016-01-11 00:00:00' AND '2016-05-12 05:10:27'
AND result = 1
GROUP BY user_id
) user_wins
ON user_wins.user_id = u.user_id
LEFT OUTER JOIN (
SELECT user_id,
COUNT(*) AS number_lost
from user_starter
WHERE created_date BETWEEN '2016-01-11 00:00:00' AND '2016-05-12 05:10:27'
AND result = -1
GROUP BY user_id
) user_losses
ON user_losses.user_id = u.user_id
LEFT OUTER JOIN (
SELECT SUM(score)
FROM user_quiz
WHERE question_answer_datetime
BETWEEN '2016-01-11 00:00:00' AND '2016-05-12 00:00:00'
GROUP BY user_id
) user_scores
ON u.id = user_scores.user_id
ORDER BY points_week DESC, u.lastname ASC, u.firstname ASC
) as sub

注:这未必是最好的结果。这在很大程度上取决于你的数据集,至于这是否一定是最好的,有时你需要做一些尝试和错误。
关于您可以在什么上使用trial and error的一个提示是我们如何查询 userGametypeAnswerDatetimeslastFrdFight以及如何查询 lastBotFightpoints_weeknumber_week_lost的结构。所有这些都可以在select语句中完成(就像前两个在我的查询中一样),也可以通过连接到子查询结果来完成(就像后三个在我的查询中一样)
混合搭配,看看什么效果最好。一般来说,当外部查询中有大量行时(在本例中是查询 number_week_win表),我发现连接到子查询的速度最快。这是因为它只需要获得一次结果,然后就可以逐个用户匹配它们。其他时候,最好将查询放在SELECT子句中—这将运行得更快,因为有更多的常量(用户id已经知道),但必须为每一行运行。所以这是一种权衡,也是为什么你有时需要使用试错法。
为什么索引工作?
所以,你可能想知道我为什么要像以前那样做索引。如果你熟悉电话簿(在这个智能手机时代,这不再是我能做的一个有效假设),那么我们可以用它作为类比:
如果您的用户表上有一个 userphonebookIndexlastnamefirstname)的复合索引(例如这里!你不需要添加索引!)你会得到一个类似电话簿的结果。(使用电子邮件而不是电话号码。)
每个索引都是整个表中数据的内部副本。在这个 email中,会在内部存储一个所有用户的列表,其中包括他们的姓氏、名字、电子邮件,每个用户都会被订购,就像电话簿一样。
为什么这样有用?考虑一下当你知道某人的名字和姓氏时。你可以快速翻到他们的姓氏所在,然后快速浏览每个人的姓氏列表,找到你想要的名字,从而获得电子邮件。
就数据库如何看待索引而言,索引的工作方式完全相同。
考虑上面定义的 phonebookIndex索引,以及如何在 userGametypeAnswerDatetimesSELECT子查询中查询该索引。
(
select MAX(question_answer_datetime)
from user_quiz
WHERE user_id = u.id and game_type = 1
) as lastFrdFight

注意我们如何将用户id(来自外部查询)和游戏类型都作为常量。这和我们前面的例子完全一样,有名字和姓氏,想要查找电子邮件/电话号码。在这种情况下,我们要寻找索引中第三个值的最大值。仍然很容易:所有的值都是有序的,所以如果这个索引位于我们前面,我们可以翻转到特定的子面板,然后用所有的cc来查看这个部分,然后选择最后一个值来找到最大值。非常快。数据库也一样。它可以非常快地找到这个值,这就是为什么您的整个查询时间减少了80%以上。
所以,这就是索引的工作原理,也是我选择这些索引的原因。
请注意,索引越多,在执行插入和更新时,速度越慢。但是,如果你从桌子上读到的比你写的要多得多,这通常是一个不可接受的权衡。
所以,试一试这些变化,让我知道它的表现。如果您需要进一步的优化帮助,请提供新的解释计划。此外,这应该会给你很多工具,让你可以使用试错法来查看哪些是有效的,哪些是无效的。我的所有更改都是相互独立的,所以你可以将它们与原来的查询片段进行交换,以查看它们是如何工作的。

关于mysql - 有什么方法可以优化此MySQL查询吗? (资源密集),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37573860/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com