【问题标题】:How to speed up the execution of sql query with multiple joins?如何通过多个连接加快 sql 查询的执行速度?
【发布时间】:2017-05-08 18:24:24
【问题描述】:

我正在开发 MySql 数据库。我需要将多个(超过 10 个)表中的信息合并到一个表中。为了做到这一点,我遵循典型的加入风格。

Select * from 
table_1 
Join table_2
on(table_1.id = table_2.id)
Join table_3
on(table_1.id = table_3.id)

它有效,但我在执行期间遭受了很多痛苦。还有其他优化我的代码的好方法吗?以下是我的代码示例:

SELECT
distinct 
u.Id, 
oc.dt,
Daily_Number_Outgoing_Calls,     
Daily_Number_Incoming_Calls,    
Daily_duration_Outgoing_Calls

FROM
creditfix.users u

JOIN

#1 Daily_No_Out_Calls
    (
        SELECT
        cl.uId,SUBSTRING(DATE,1,10) as dt,
        count(1) as Daily_Number_Outgoing_Calls

        From creditfix.call_logs as cl
            WHERE
                cl.`type`=2 #out going calls only
        GROUP by cl.uId,dt
    ) oc
    ON (u.Id=oc.Uid)

#2 Daily_No_In_Calls
    JOIN
    (
        SELECT
        cl.uId, SUBSTRING(DATE,1,10) as dt,
        count(1) as Daily_Number_Incoming_Calls
        From creditfix.call_logs as cl
        WHERE
            cl.`type`=1 #incoming calls only
        GROUP by cl.uId,dt
    ) ic
    ON (u.Id=ic.Uid)

#3 Daily_duration_Out_Calls
     JOIN
    (
        SELECT
        cl.uId,SUBSTRING(DATE,1,10) as dt, 
        (sum(duration)) as Daily_duration_Outgoing_Calls
        From creditfix.call_logs as cl
        WHERE
            cl.`type`=2 #out going calls only
        GROUP by cl.uId,dt
    ) od
    ON (u.Id=od.uid)
    # It goes on like this...

【问题讨论】:

  • 请改掉在GROUP BY 中使用列号而不是名称的习惯,这会使查询更难阅读。
  • 而且当你编辑代码时会变得脆弱,以防你在SELECT列表中插入列。
  • 性能问题是因为您不仅要加入表,还要加入子查询。它们不像真正的表那样有索引,所以它们很难让 MySQL 优化。
  • @Barmar 嗨,我是新手并在我的代码中进行了编辑 :) 感谢您的 cmets
  • @Barmar 有没有办法为此类查询编制索引?

标签: mysql sql join subquery dbeaver


【解决方案1】:

看起来您不需要为每列使用单独的子查询,您应该能够在单个子查询中完成它们。

我也不认为您应该在主查询中需要DISTINCT

SELECT 
    u.Id, 
    cl.dt,
    cl.Daily_Number_Outgoing_Calls,     
    cl.Daily_Number_Incoming_Calls,    
    cl.Daily_duration_Outgoing_Calls,   
    cl.Daily_duration_Incoming_Calls #.... for keep on adding like this

FROM creditfix.users u
JOIN (
    SELECT uId, SUBSTRING(DATE, 1, 10) AS dt,
        SUM(`type`=2) AS Daily_Number_Outgoing_Calls,
        SUM(`type`=1) AS Daily_Number_Incoming_Calls,
        SUM(IF(`type`=2, duration, 0)) AS Daily_duration_Outgoing_Calls,
        SUM(IF(`type`=1, duration, 0)) AS Daily_duration_Incoming_Calls
    FROM creditfix.call_logs as cl
    GROUP BY uId, dt) AS cl
ON u.Id = cl.uId

请参阅multiple query same table but in different columns mysql,了解子查询中用于获取所有计数的逻辑。

【讨论】:

  • 绝对是个好主意,但出于好奇,为什么要使用子查询呢?似乎没有它会更容易理解。
  • 1 个可能的原因是 creditfix.users 与 call_logs 之间存在多对多。通过在加入之前进行聚合,您可以消除人为增加计数的风险。
  • @xQbert 这在某些查询中是个问题,但在这里我们似乎使用唯一 ID 加入,并且我们通过该外键对子查询进行分组。更普遍的原因是聚合后连接会减小连接的大小。
【解决方案2】:

正如 cmets 中提到的,这些不是简单的连接,它们是子查询连接,这使得优化更加困难。您必须优化每个子查询,或者想办法不需要子查询。

由于您想获取每个用户的通话记录信息并输入给定日期,这可以通过简单的加入和分组来完成。不需要子查询。

select
    ???
from
    creditfix.users u
join
    creditfix.call_logs as cl on u.id = cl.uid
where
    substring(date,1,10)=???
group by
    cl.uid, cl.type;

因此,要复制您的目标,调用次数及其总持续时间...

select
    u.id, cl.type, count(cl.id) as num_calls, sum(cl.duration) as duration
from
    creditfix.users u
join
    creditfix.call_logs as cl on u.id = cl.uid
where
    substring(date,1,10)='2017-03-18'
group by
    cl.uid, cl.type;

你会得到这样的东西。

+----+------+-----------+---------------+
| id | type | num_calls | call_duration |
+----+------+-----------+---------------+
|  1 |    1 |         3 |            20 |
|  1 |    3 |         1 |            10 |
|  1 |    5 |         2 |             4 |
|  2 |    5 |         1 |             4 |
+----+------+-----------+---------------+

这失去了命名每个单独列的能力,但这是接收查询的任何东西都可以处理的事情。或者可以使用单个子查询来处理。

types 可以用case 命名...

case cl.type
    when 1 then 'outgoing'
    when 2 then 'incoming'
    when 3 then ...
    else cl.type
end as type

...但这需要在查询中硬编码幻数。您最好制作一个表格来存储有关类型的信息并加入其中。


子查询本身在这里存在潜在的性能问题:substring(date,1,10) = '2017-03-08'。如果date 没有被索引,查询将不得不进行全表扫描。

date 作为字符串会引入性能问题。数据库必须对每一行执行字符串操作,尽管 MySQL 可能足够聪明以使用索引。而datetime type 是一个简单的数字比较,将使用索引。它也小一点,8 个字节。

它允许您使用the date and time functions without convertingSUBSTRING(DATE,1,10) 可以替换为更快更安全的date(date)

同时命名列date 是个坏主意,它是 MySQL 中的函数名,可能会导致问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-07
    • 2019-11-30
    • 1970-01-01
    • 1970-01-01
    • 2015-09-23
    • 2022-01-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多