【问题标题】:How to optimize the mysql query with more the 20 millions records如何使用超过 2000 万条记录优化 mysql 查询
【发布时间】:2020-03-25 05:06:26
【问题描述】:

我的项目使用 MySQL,并且在“mixpanel_data”表中有超过 2000 万条记录。

因此,当我尝试获取过去 6 个月的记录时,它会中断请求。它只为我提供了最近 5-10 天的记录

我正在使用以下 MySQL 查询。

SELECT  `sb_users`.`id`,`sb_users`.`name`, SUM(`mixpanel_data`.duration) as timeCount,
        COUNT(`mixpanel_data`.spread_id) as PageCount,`mixpanel_data`.`language`,
        `mixpanel_data`.`created_at`, `mixpanel_data`.`book_name`,
        `mixpanel_data`.`email`, `mixpanel_data`.`ip_address`,
        `mixpanel_data`.`event_date`, `mixpanel_data`.`type`,
        'Read', `mixpanel_data`.`unique_session_id`, `mixpanel_data`.`operating_system`,
        `mixpanel_data`.`country`, `mixpanel_data`.`region`, `mixpanel_data`.`city`,
        `mixpanel_data`.`device`, `mixpanel_data`.`browser`,
        `mixpanel_data`.`browser_version`
    FROM  `mixpanel_data`
    LEFT JOIN  `sb_users`  ON `mixpanel_data`.`first_name` = `sb_users`.`username`
    WHERE  `mixpanel_data`.`email` !=''
      AND  `mixpanel_data`.`created_at` Between '2019-03-24' AND '2020-03-24'
      and  `mixpanel_data`.`action` IN('PauseAudio')
    GROUP BY  `mixpanel_data`.`email`, `mixpanel_data`.`book_name` ,
        `mixpanel_data`.`language`
 UNION 
 SELECT  `sb_users`.`id`,`sb_users`.`name`, SUM(`mixpanel_data`.duration) as timeCount,
        COUNT(`mixpanel_data`.spread_id) as PageCount,`mixpanel_data`.`language`,
        `mixpanel_data`.`created_at`, `mixpanel_data`.`book_name`,
        `mixpanel_data`.`email`, `mixpanel_data`.`ip_address`,
        `mixpanel_data`.`event_date`, `mixpanel_data`.`type`,
        'Read', `mixpanel_data`.`unique_session_id`, `mixpanel_data`.`operating_system`,
        `mixpanel_data`.`country`, `mixpanel_data`.`region`, `mixpanel_data`.`city`,
        `mixpanel_data`.`device`, `mixpanel_data`.`browser`,
        `mixpanel_data`.`browser_version`
    FROM  `mixpanel_data`
    LEFT JOIN  `sb_users`  ON `mixpanel_data`.`first_name` = `sb_users`.`username`
    WHERE  `mixpanel_data`.`email` !=''
      AND  `mixpanel_data`.`created_at` Between '2019-03-24' AND '2020-03-24'
      and  `mixpanel_data`.`action` NOT IN('PlayAudio','PauseAudio')
      AND  `mixpanel_data`.`spread_id` !=''
    GROUP BY  `mixpanel_data`.`email`, `mixpanel_data`.`book_name` ,
        `mixpanel_data`.`language`

我尝试使用以下查询更改我的查询,但它不适合我。它还破坏了请求,并且给我的记录也更少。

SELECT sb_users.id,
sb_users.NAME,
Count(mixpanel_data.spread_id) AS PageCount,
SUM(CASE When action IN ('PauseAudio') Then duration Else 0 End) as total, SUM(CASE When action NOT IN ('PlayAudio', 'PauseAudio') Then duration Else 0 End) as Sectotal,
mixpanel_data.language,
mixpanel_data.created_at,
mixpanel_data.book_name,
mixpanel_data.email,
mixpanel_data.ip_address,
mixpanel_data.event_date,
mixpanel_data.type,
'Read',
mixpanel_data.unique_session_id,
mixpanel_data.operating_system,
mixpanel_data.country,
mixpanel_data.region,
mixpanel_data.city,
mixpanel_data.device,
mixpanel_data.browser,
mixpanel_data.browser_version
FROM `mixpanel_data`
LEFT JOIN sb_users
ON `mixpanel_data`.`first_name` = `sb_users`. `username`
WHERE
mixpanel_data.email != '' AND mixpanel_data.`created_at` Between '2019-03-24' AND '2020-03-24'
AND `mixpanel_data`.`spread_id` !='' GROUP BY mixpanel_data.email,
mixpanel_data.book_name,
mixpanel_data.language

我还尝试在 first_name、username 和 created_at 列上添加索引器。但是查询需要很多时间(超过 15-16 秒)

谁能帮我优化查询?

【问题讨论】:

  • “它破坏了请求。” 是什么意思?然后只发送一部分结果?这听起来不对。
  • 您的查询无效。您按email, book_name, language 分组,但例如选择browser_version。组的哪个浏览器版本?最伟大的?最小的? event_datetyperegion 等也是如此。由于您似乎对聚合组的概念感到困惑,我建议您使用 SET sql_mode = 'ONLY_FULL_GROUP_BY'; 来帮助您构建有效的查询。 (如果没有该设置,MySQL 通过返回任意值来规避查询的缺陷,这通常意味着不希望的结果。)
  • 如果您希望每个组的浏览器版本、类型等都相同,那么您的数据模型甚至存在问题,应该在编写任何查询之前对其进行规范化。

标签: mysql sql indexing


【解决方案1】:

sb_users 上的此索引可能会有所帮助:INDEX(username, name, id)

UNION 切换到UNION ALL 应该会加快查询速度。但这可能会导致重复行。

您正在获取一年的数据;那是桌子的百分之几?如果这是一个很大的百分比,那么mixpanel_data 上的任何索引都不会有用。

GROUP BY 可能不合适,除非您有 UNIQUE(book_name, email, language)。因为,如果用户从两个不同的 ip_addresses 看同一本书怎么办?查询将传递哪个 ip_address?

假设SELECTs 之间的唯一区别是

      and  ma.`action` IN('PauseAudio')

      and  ma.`action` NOT IN('PlayAudio','PauseAudio')
      AND  ma.`spread_id` !=''

请考虑以下事项——如果您使用的是 MySQL 8.0:

WITH cte AS
        SELECT ...
            FROM  `mixpanel_data` AS ma
            LEFT JOIN  `sb_users` AS su
               ON ma.`first_name` = su.`username`
            WHERE  ma.`email` !=''
              AND  ma.`created_at` >= '2019-03-24'
              AND  ma.`created_at`  < '2019-03-24' + INTERVAL 1 YEAR
SELECT * FROM cte
      WHERE `action` IN('PauseAudio')
UNION ALL
SELECT * FROM cte
      WHERE `action` NOT IN('PlayAudio','PauseAudio')
        AND `spread_id` !=''
GROUP BY  `email`, `book_name` , `language`

希望这会导致只扫描一次mixpanel_data

另一种方法(并且不依赖于 8.0):使用 mixpanel_data 进行内部查询(“派生表”),然后 JOINsb_users

 SELECT ...
     FROM ( SELECT ...
               FROM mixpanel_data
               -- (no JOIN)
               WHERE ...
               GROUP BY ...
            UNION ALL
               FROM mixpanel_data
               -- (no JOIN)
               WHERE ... (the other)
               GROUP BY ...
          )
    LEFT JOIN sb_users ON ...

这样做的好处是它不会在所有工作中拖拽 sb_user 数据。取而代之的是它从 sb_user 通过 GROUP BY 缩小行数之后获取。

可能会有更多提示;请执行上述操作,然后提供EXPLAIN SELECT 和一些表格大小。

这是第一个查询。 第二个查询在几个方面有所不同:

  • spread_id 的测试缺失
  • UNION 的重复数据删除可能会导致一组不同的行。

我对@9​​87654341@ 到sb_users 的建议执行GROUP BY 之后也适用于此(尽管没有UNION)。

调试

  1. SELECT ... FROM mixpanel_data 没有SUMCOUNTGROUP BY,但添加LIMIT。查看数据是否符合您的预期。
  2. 添加SUMCOUNTGROUP BY;关注这些结果。
  3. 然后JOIN 到另一张桌子。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多