【问题标题】:Optimise MySQL - JOIN vs Nested query优化 MySQL - JOIN 与嵌套查询
【发布时间】:2017-03-05 17:17:59
【问题描述】:

基于联接表比嵌套查询更有效的假设,我一直在尝试优化一些 SQL 查询。我多次加入同一个表以对数据执行不同的分析。

我有 2 张桌子:

交易:

id    |   date_add    |   merchant_ id    | transaction_type      |     amount
1         1488733332          108                  add                     20.00
2         1488733550          108                 remove                   5.00

还有一个只列出日期的日历表,以便我可以在特定日期没有交易的情况下创建空记录:

日历:

id     |    datefield
1           2017-03-01
2           2017-03-02
3           2017-03-03
4           2017-03-04

我在交易表中有数千行,我正在尝试获取每月总交易和不同类型交易的年度摘要(即总共 12 行),其中

  • 交易 = 所有“金额”的总和,
  • additions = 所有“金额”的总和,其中 transaction_type = “add”
  • 赎回 = 所有“金额”的总和,其中 transaction_type = “删除”

结果:

month     |    transactions     |    additions  |   redemptions
Jan                15                  12               3
Feb                20                  15               5
...

我的初始查询如下所示:

SELECT  COALESCE(tr.transactions, 0) AS transactions, 
        COALESCE(ad.additions, 0) AS additions, 
        COALESCE(re.redemptions, 0) AS redemptions, 
        calendar.date 
FROM (SELECT DATE_FORMAT(datefield, '%b %Y') AS date FROM calendar WHERE datefield LIKE '2017-%' GROUP BY YEAR(datefield), MONTH(datefield)) AS calendar 
LEFT JOIN (SELECT COUNT(transaction_type) as transactions, from_unixtime(date_add, '%b %Y') as date_t FROM transactions WHERE merchant_id = 108  GROUP BY from_unixtime(date_add, '%b %Y')) AS tr
ON calendar.date = tr.date_t
LEFT JOIN (SELECT COUNT(transaction_type = 'add') as additions, from_unixtime(date_add, '%b %Y') as date_a FROM transactions WHERE merchant_id = 108  AND transaction_type = 'add' GROUP BY from_unixtime(date_add, '%b %Y')) AS ad
ON calendar.date = ad.date_a
LEFT JOIN (SELECT COUNT(transaction_type = 'remove') as redemptions, from_unixtime(date_add, '%b %Y') as date_r FROM transactions WHERE merchant_id = 108  AND transaction_type = 'remove' GROUP BY from_unixtime(date_add, '%b %Y')) AS re
ON calendar.date = re.date_r

我尝试对其进行优化和清理,删除嵌套语句并提出以下建议:

SELECT 
    DATE_FORMAT(cal.datefield, '%b %d') as date,
    IFNULL(count(ct.amount),0) as transactions, 
    IFNULL(count(a.amount),0) as additions, 
    IFNULL(count(r.amount),0) as redeptions
FROM calendar as cal 
LEFT JOIN transactions as ct ON cal.datefield = date(from_unixtime(ct.date_add))  && ct.merchant_id = 108
LEFT JOIN transactions as r ON r.id = ct.id && r.transaction_type = 'remove'
LEFT JOIN transactions as a ON a.id = ct.id && a.transaction_type = 'add' 
WHERE cal.datefield like '2017-%'
GROUP BY month(cal.datefield)

我惊讶地发现修改后的语句比我的数据集的原始语句慢了大约 20 倍。我错过了某种逻辑吗?考虑到我要多次加入同一个表,是否有更好的方法通过更简化的查询来实现相同的结果?

编辑: 因此,为了进一步解释我正在寻找的结果 - 我希望一年中的每个月都有一行(12 行),每个月有一列用于显示每个月的总交易、总添加和总赎回。

第一个查询我在大约 0.5 秒内得到结果,但第二个查询我在 9.5 秒内得到结果。

【问题讨论】:

  • 您可以添加解释并发布优化和非优化查询的结果吗?
  • 我真的不会为此烦恼日历表
  • 我看到你在第二个查询中使用了&&,在 LEFT JOIN 的 ON 语句中?他们应该是AND
  • 您能解释一下结果集中的列吗?我相信这个任务可以用一种不太复杂的方式来解决。
  • @PaulSpiegel 我在那里添加了一些编辑来解释预期结果

标签: mysql join optimization


【解决方案1】:

查看您的查询您可以使用单个左连接与 case when

SELECT  COALESCE(t.transactions, 0) AS transactions, 
        COALESCE(t.additions, 0) AS additions, 
        COALESCE(t.redemptions, 0) AS redemptions, 
        calendar.date 
FROM (SELECT DATE_FORMAT(datefield, '%b %Y') AS date 
          FROM calendar 
          WHERE datefield LIKE '2017-%' 
          GROUP BY YEAR(datefield), MONTH(datefield)) AS calendar 
LEFT JOIN 
 ( select 
      COUNT(transaction_type) as transactions
      , sum( case when transaction_type = 'add' then 1 else 0 end ) as additions
      , sum( case when transaction_type = 'remove' then 1 else 0 end ) as redemptions
      ,  from_unixtime(date_add, '%b %Y') as date_t 
      FROM transactions 
      WHERE merchant_id = 108  
      GROUP BY from_unixtime(date_add, '%b %Y' ) t ON calendar.date = t.date_t

【讨论】:

  • 感谢@scaisEdge 案例何时是一个启示 - 非常感谢!这从 0.5 秒缩短到 0.2
  • 第一个SUM可以简化为SUM(transaction_type = 'add')
  • 如果datefield上有索引,datefield LIKE '2017-%' 可以通过datefield >= '2017-01-01' AND datefield < '2017-01-01' + INTERVAL 1 YEAR加速。
  • 我很确定COALESCE 是不必要的——无论如何SUM( nothing matches )0
  • @RickJames 我重构了左连接 .. 让主选择由 th OP 完成 ..
【解决方案2】:

首先,我将从您的calendar 表中创建一个包含每个月时间戳范围的派生表。这样,如果 date_add 被索引,则与 transactions 表的连接将是有效的。

select month(c.datefield) as month, 
       unix_timestamp(timestamp(min(c.datefield), '00:00:00')) as ts_from,
       unix_timestamp(timestamp(max(c.datefield), '23:59:59')) as ts_to
from calendar c
where c.datefield between '2017-01-01' and '2017-12-31'
group by month(c.datefield)

将其与transaactions 表连接并使用条件聚合来获取您的数据:

select c.month,
       sum(t.amount) as transactions,
       sum(case when t.transaction_type = 'add'    then t.amount else 0 end) as additions,
       sum(case when t.transaction_type = 'remove' then t.amount else 0 end) as redemptions
from (
    select month(c.datefield) as m, date_format(c.datefield, '%b') as `month`
           unix_timestamp(timestamp(min(c.datefield), '00:00:00')) as ts_from,
           unix_timestamp(timestamp(max(c.datefield), '23:59:59')) as ts_to
    from calendar c
    where c.datefield between '2017-01-01' and '2017-12-31'
    group by month(c.datefield), date_format(c.datefield, '%b')
) c
left join transactions t on t.date_add between c.ts_from and c.ts_to
where t.merchant_id = 108
group by c.m, c.month
order by c.m

【讨论】:

  • 如果使ORDER BYGROUP BY 匹配,可能会更快一些。这可以避免额外的排序。
  • @RickJames 您认为优化器无法“看到”ORDER BYGROUP BY 的子集?然而,这并不重要,因为结果集只包含 12 行。我这里还有一个问题。此查询在 1M 行表上需要 2 秒(2014 年约为 330K 行)。但是更改为内部连接它在 100 毫秒内运行。所以我需要把代码放到另一个子查询中。 MySQL 优化器有时真的很愚蠢。
猜你喜欢
  • 2011-02-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-30
  • 1970-01-01
相关资源
最近更新 更多