【问题标题】:Optimizing MySQL query that does 3 counts and group by优化执行 3 个计数和分组的 MySQL 查询
【发布时间】:2020-01-02 11:34:36
【问题描述】:

大家好,

我正在努力解决以下问题,我非常感谢有关如何提高此查询性能的一些建议

SELECT 
   COUNT(*) AS `pageviews`, 
   COUNT(DISTINCT `sessions_events`.`session_id`) AS `sessions`, 
   COUNT(DISTINCT `sessions_events`.`visitor_id`) AS `visitors`,
   `sessions_events`.`date_day` 
FROM 
   `sessions_events`
LEFT JOIN
   `websites_visitors` ON `sessions_events`.`visitor_id` = `websites_visitors`.`visitor_id`
WHERE 
    `sessions_events`.`website_id` = 1
    AND (`sessions_events`.`date_day` BETWEEN '2019-12-01' AND '2019-12-31')
GROUP BY
    `sessions_events`.`date_day`

我试图从中获取的表的当前状态如下所示:

我最初是 DATE_FORMAT(sessions_events.date, '%Y-%m-%d') AS formatted_date 并按 formatted_date 分组> 但我还创建了另一个 date_day,它只存储实际日期(没有 H:I:S)并摆脱 DATE_FORMAT() 的使用。 p>

此表 (sessions_events) 现在已在 5 个不同网站(每个 website_id 约 100 万行)中填充了 500 万行,因为我想测试性能。

完成上述查询大约需要 13-15 秒

如果您询问 LEFT JOIN:我正在使用它,以防前端有人想要对选择应用过滤器,并且只检查从美国访问过的综合浏览量、会话和访问者(例如)。

这是我需要的数据的样子:

此数据用于生成显示特定日期范围内的综合浏览量、会话和访问者的图表。

对此的任何帮助将不胜感激,因为我只是看不出如何改进这一点..

再次感谢您!

【问题讨论】:

  • 这很好。我认为你能期望的最好的结果是对 (visitor_id,website_id,date) 的某种排列的复合索引
  • 感谢@Strawberry 的评论!尝试了这个,但不幸的是性能没有真正的变化。在您看来,这个性能是您能得到的最多的吗?
  • 你试过Explaindev.mysql.com/doc/refman/5.7/en/using-explain.html来找出可能的瓶颈吗?
  • @BrainFooLong Yes -> i.imgur.com/hlqR9kb.png 但我个人无法从这个解释中找到任何额外的东西..
  • 根据解释,它正在检查 210 万行,即使在索引优化搜索之后也是如此。无论如何,这将需要一段时间。也许剩下的唯一解决方案是 (a) 获得更快的服务器并为 InnoDB 缓冲池分配更多 RAM,或者 (b) 使用汇总表。

标签: php mysql


【解决方案1】:

您应该做的是创建一个包含字段website_iddate_day 的复合索引。这应该会为您加快查询速度。

ALTER TABLE `sessions_events`
  ADD INDEX `website_id_date_day` (`website_id` ASC, `date_day` ASC);

编辑

经过聊天,发现了一个修复方法,添加了两个索引而不是上面的一个,并重写了查询:

ALTER TABLE `sessions_events`
  ADD INDEX `website_id_date_day_session_id` (`website_id` ASC, `date_day` ASC, `session_id` ASC);

ALTER TABLE `sessions_events`
  ADD INDEX `website_id_date_day_visitor_id` (`website_id` ASC, `date_day` ASC, `visitor_id` ASC);


SELECT
  COUNT(*) AS `pageviews`,
  (
    SELECT
      COUNT(DISTINCT(`tmp`.`session_id`))
    FROM
      `sessions_events` AS `tmp`
    WHERE
      `sessions_events`.`website_id` = `tmp`.`website_id`
      AND `sessions_events`.`date_day` = `tmp`.`date_day`
  ) AS `sessions`,
  (
    SELECT
      COUNT(DISTINCT(`tmp`.`visitor_id`))
    FROM
      `sessions_events` AS `tmp`
    WHERE
      `sessions_events`.`website_id` = `tmp`.`website_id`
      AND `sessions_events`.`date_day` = `tmp`.`date_day`
  ) AS `visitors`,
  `sessions_events`.`date_day`
FROM
  `sessions_events`
WHERE
  `sessions_events`.`website_id` = 1
  AND (`sessions_events`.`date_day` BETWEEN '2019-12-01' AND '2019-12-31')
GROUP BY
  `sessions_events`.`date_day`

这使得查询使用子查询,而子查询又可以使用添加的索引。

【讨论】:

  • 感谢您的评论,我已经按照另一条评论的建议进行了尝试:stackoverflow.com/questions/59562653/… 但不幸的是,我看不到任何性能改进,因为查询仍然需要 13-15 秒才能执行。在这个特定的查询中,LEFT JOIN 确实可以被删除,但它仍然存在,因为可以应用链接到“websites_visitors”表的其他过滤器。
  • 我错过了那条评论。而且我在您的表索引屏幕截图中没有看到复合索引。你确定它在那里?
  • 不用担心^_^我在其他人的评论之后添加了综合索引,因为我最初没有测试过,这就是为什么它不在最初的截图中。
  • 我认为@Strawberry 建议在复合索引中包含visitor_id 是不必要的,并且可能会使索引对您的查询无用。再试一次,去掉visitor_id,就像我在回答中写的那样。
  • 刚刚做了,由于某种原因性能较慢。添加您建议的复合索引后,我应该删除任何以前的索引吗? i.imgur.com/q2XbgGj.png
【解决方案2】:

嘿,fabian,我不确定,但是通过创建视图来检查你可以检查性能 通过运行:

select * from get_chart

代码:

create view get_chart
as
    "query or table where you set all data"

举个例子

CREATE VIEW [Brazil Customers] 
AS
    SELECT CustomerName, ContactName
    FROM Customers
    WHERE Country = "Brazil";

【讨论】:

  • 在低效查询的基础上再增加一层复杂性不会提高其性能。
  • naval 可能不知道 MySQL 视图不是物化视图。视图每次只在视图定义中运行 SELECT 查询。它更像是一个宏。
  • 谢谢比尔,这就是为什么我在回答中说我不确定
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-05-19
  • 2020-05-21
  • 2011-05-22
  • 2013-04-16
  • 2012-08-06
  • 2018-06-18
  • 1970-01-01
相关资源
最近更新 更多