选择查询需要太长时间

时间:2017-03-20 18:03:52

标签: php mysql

这两个查询需要太长时间才能产生结果(有时1分钟甚至有时会因某些错误而结束)并且在服务器上施加了很大的负担:

("SELECT SUM(`rate`) AS `today_earned` FROM `".PREFIX."traffic_stats` WHERE `userid` = ?i AND from_unixtime(created) > CURRENT_DATE ORDER BY created DESC", $user->data->userid)

("SELECT COUNT(`userid`) AS `total_clicks` FROM `".PREFIX."traffic_stats` WHERE `userid` = ?i", $user->data->userid)

该表有大约400万行。

这是表结构: enter image description here

我在traffic_id上有一个索引:

enter image description here

如果您从traffic_stats表中选择任何内容,则需要永久保留,但插入此表是正常的。

是否可以减少执行此查询所花费的时间?我使用PDO,我是这一切的新手。

2 个答案:

答案 0 :(得分:2)

ORDER BY将花费大量时间,因为您只需要汇总数据(添加数字或计数是可交换的),ORDER BY将进行大量无用的排序,花费您的时间和服务器功率。

您需要确保索引正确,您可能需要 user_id (user_id,已创建)的索引。

user_id是否为数字?如果没有,那么您可以考虑将其转换为数字类型,例如int。

这些正在改进您的查询和结构。但是,让我们改进这个概念。插入和修改是否非常频繁?您是否绝对需要实时数据,或者您也可以使用准实时数据?

如果插入/修改不是很频繁,或者您可以使用较旧的数据,或者问题导致了巨大的麻烦,那么您可以通过定期运行计算这些值并缓存它们的cron作业来完成此操作。应用程序将从缓存中读取它们。

答案 1 :(得分:0)

我不确定为什么你接受了答案,当你真的没有理解问题的核心时。

我还想澄清这是一个mysql问题,而且你在使用PDO或PHP的事实并不重要。

人们建议你使用EXPLAIN。我会更进一步告诉你,你需要使用EXPLAIN EXTENDED格式= json选项来全面了解正在发生的事情。看看你的解释屏幕截图,你应该跳出来的是查询超过1米的行来得到答案。这就是你的查询花了这么长时间的原因!

在一天结束时,如果您已正确索引表,那么您的目标应该是在这样的大表中,以便检查的行数与最终结果集非常接近。

让我们看看第二个查询,这很简单:

("SELECT COUNT(`userid`) AS `total_clicks` FROM `".PREFIX."traffic_stats` WHERE `userid` = ?i", $user->data->userid)

在这种情况下,唯一真正重要的是你有一个关于traffic_stats.userid的索引。

我建议,如果此时您不确定,请删除原始主键(traffic_id)索引以外的所有索引,并仅从userid列上的索引开始。运行您的查询。结果是什么,需要多长时间?看看EXPLAIN EXTENDED。鉴于查询的简单性,您应该看到只使用索引并且行应该与结果匹配。

现在进行第一次查询:

("SELECT SUM(`rate`) AS `today_earned` FROM `".PREFIX."traffic_stats` WHERE `userid` = ?i AND from_unixtime(created) > CURRENT_DATE ORDER BY created DESC", $user->data->userid)

查看WHERE子句有以下标准:

  • userid =
  • from_unixtime(已创建)> CURRENT_DATE

您已经在userid上有一个索引。尽管先前给出了建议,但在创建的userid上有一个索引并不一定正确,在你的情况下它没有任何价值。

原因是您正在使用from_unixtime(created)的mysql函数来转换已创建列的原始值。

无论何时执行此操作,都无法使用索引。如果您使用的是本机TIMESTAMP类型,那么在与CURRENT_DATE进行比较时不会有任何顾虑,但在这种情况下,为了处理不匹配,您只需要转换CURRENT_DATE而不是创建的列。

您可以将CURRENT_DATE作为参数传递给UNIX_TIMESTAMP。

mysql> select UNIX_TIMESTAMP(), UNIX_TIMESTAMP(CURRENT_DATE);
+------------------+------------------------------+
| UNIX_TIMESTAMP() | UNIX_TIMESTAMP(CURRENT_DATE) |
+------------------+------------------------------+
|       1490059767 |                   1490054400 |
+------------------+------------------------------+
1 row in set (0.00 sec)

从这个快速示例中可以看出,UNIX_TIMESTAMP本身将是当前时间,但CURRENT_DATE本质上是一天的开始,这显然是您正在寻找的。

我愿意打赌,当前日期的行数将少于系统历史记录中用户的总行数,所以这就是为什么你不想要索引的原因用户,在接受的答案中按照先前的建议创建。您可能受益于已创建的用户ID的索引。

我的建议是分别从每个列的单个索引开始。

("SELECT SUM(`rate`) AS `today_earned` FROM `".PREFIX."traffic_stats` WHERE `userid` = ?i AND created > UNIX_TIMESTAMP(CURRENT_DATE)", $user->data->userid)

使用重写的查询,再次假设结果集相对较小,您应该看到一个干净的EXPLAIN,其行与您的最终结果集匹配。

至于您是否应该应用ORDER BY,这不应该是出于性能原因而消除的,而是因为它与您期望的结果无关。如果您需要或想要用户订购的结果,请将其保留。除非您生成大型结果集,否则它不应成为主要问题。

对于那个特定的查询,因为你正在做一个SUM(),所以没有ORDERING数据的值,因为你只会得到一行,所以在这种情况下我同意Lajos,但是有很多次你可能正在使用GROUP BY,在这种情况下,你可能希望订购最终结果。