使用JOIN,MySQL GROUP BY速度较慢

时间:2018-01-05 22:22:53

标签: mysql sql query-optimization

我们正在努力实现的目标:

基本上我们正在收集一些关于用户的一对一元数据(名称,地址),然后我们会对他们的订单进行一些摘要报告。

查询1

SELECT
    -- STUDENT DATA
       wp_users.user_email AS 'email',

    -- STUDENT METADATA
       um_fn.meta_value AS 'first_name',
       um_ln.meta_value AS 'last_name',
       ### MANY MORE ###

    -- ORDER DATA
       MAX(pmt_orders.order_date) last_order,
       MIN(pmt_orders.order_date) first_order,
       COUNT(pmt_order_course.fk_order_id) life_courses,
       ### MANY MORE AGGREGATE FUNCTIONS ###

  FROM wp_users

      ### LEFT OUTER JOINS, INNER JOINS, LEFT JOINS (FOR THE AGGREGATE FUNCTIONS) ###

    -- STUDENT METADATA
       LEFT JOIN wp_usermeta um_fn ON wp_users.id = um_fn.user_id AND um_fn.meta_key = 'shipping_first_name'
       LEFT JOIN wp_usermeta um_ln ON wp_users.id = um_ln.user_id AND um_ln.meta_key = 'shipping_last_name'
       ### MANY MORE ###

 WHERE pmt_order_course.unenroll_date IS NULL OR pmt_order_course.unenroll_date = '0000-00-00'

 GROUP BY wp_users.user_email

时间:13秒

我们开始调查并将其分解为一个元数据查询(0.5秒)和其他内容(2秒)。基本上只是将列拆分为两个单独的查询。

注意:我确实尝试将每个非聚合选择添加到GROUP BY中,以便我们符合严格模式。对绩效的零影响。

查询2

困惑,然后我们将其转回一个大型查询。方法是将非聚合选择移动到外部选择中。

SELECT users.*,

    -- STUDENT METADATA
       um_fn.meta_value AS 'first_name',
       um_ln.meta_value AS 'last_name',
       ### MANY MORE ###

  FROM (

SELECT
    -- STUDENT DATA
       wp_users.ID,
       wp_users.user_email AS 'email',

    -- ORDER DATA
       MAX(pmt_orders.order_date) last_order,
       MIN(pmt_orders.order_date) first_order,
       COUNT(pmt_order_course.fk_order_id) life_courses,
       ### MANY MORE AGGREGATE FUNCTIONS ###

  FROM wp_users

       ### LEFT OUTER JOINS, INNER JOINS, LEFT JOINS (FOR THE AGGREGATE FUNCTIONS) ###

 WHERE pmt_order_course.unenroll_date IS NULL OR pmt_order_course.unenroll_date = '0000-00-00'

 GROUP BY wp_users.user_email

       ) AS users

    -- STUDENT METADATA
       LEFT JOIN wp_usermeta um_fn ON users.id = um_fn.user_id AND um_fn.meta_key = 'shipping_first_name'
       LEFT JOIN wp_usermeta um_ln ON users.id = um_ln.user_id AND um_ln.meta_key = 'shipping_last_name'
       ### MANY MORE ###

时间:2秒

结果

查询2产生相同的结果,在数学上等同于查询1.它在2秒内运行。

我可以理解为什么,MySQL会为每个订单查找一次元数据然后按用户进行聚合,而不是每个用户查找一次元数据。

一些分析数据:wp_users表很大,聚合行大约是每个用户两行。

问题

为什么MySQL优化器本身不能解决这个问题?有没有其他方法可以编写看起来更具表现力的查询(如查询1),同时使MySQL使用查询2的更快的执行路径?

3 个答案:

答案 0 :(得分:1)

我要说的是,在查询1中请记住,您在users表中加入了其余未聚合查询记录的次数。

但是,在查询2中,您只加入聚合数据的次数。

这就是它与众不同的原因。

如果您只需要名字和姓氏,我猜您可以使用子查询来获取名字和姓氏,而不是um_fn.meta_value,但如果选项2很快,您可能最好离开它一个人(或添加没有人会读的评论)。在查询1中可以尝试一些可能更具可读性的东西吗?

rxjs

答案 1 :(得分:0)

第一个查询是否有适当的索引?

尝试添加:

ALTER TABLE `wp_usermeta` ADD INDEX `wp_usermeta_index_1` (`meta_key`, `user_id`, `meta_value`);
ALTER TABLE `wp_users` ADD INDEX `wp_users_index_1` (`id`, `user_email`);

此外,只要有GROUP BY子句,就可以添加显式ORDER BY子句。如果排序没有意义,请添加ORDER BY NULL以避免冗余订单(这会降低性能)。

答案 2 :(得分:0)

问题1:优化器没有(或不能)执行您想象的所有优化。为什么不?你可以免费获得很多好东西。在MySQL中发布更多免费内容需要数年时间。

问题2:"爆炸 - 内爆" - JOINs增加要查看的行数,然后GROUP BY缩减回到您开始时的行数。 Ctznkane525部分解决了这个问题。

问题3:键值(EAV)架构真的很糟糕;除了放弃这个概念之外,没有什么能解决它。

问题4:WP" meta"数据是EAV。见问题2.

问题5:WP在wp_postmeta上有糟糕的索引。部分解决方案:http://mysql.rjweb.org/doc.php/index_cookbook_mysql#speeding_up_wp_postmeta(也许" post_id"需要替换为" user_id"在该引用中。)请注意,这超出了Tomer的建议。

问题6:MySQL没有答案"摘要表"。我不确定这对你有用,但这是一个讨论:http://mysql.rjweb.org/doc.php/summarytables