提高性能:非常慢的Oracle SQL Join

时间:2016-09-28 08:24:32

标签: sql oracle performance join

我是SQL查询的新手,我花了3个小时来获得加入2个查询的全部结果。 我专注于使用左连接,并在研究后避免在select语句中使用子查询。但它仍然非常慢。我没有亲密的朋友谁知道sql足以解释什么是错的或我接近我应采取的方法。 我也是新来的,所以如果不允许这个问题,请通知我,我会立即将其删除。

这是查询的结构...... 第一个查询将获取成员详细信息。 第二个查询将获取交易详细信息。 关系是, 一个产品有很多子计划,有很多成员。 一种产品还有许多基于每种产品的交易。 我需要显示所有交易并复制每个成员的每一行。 我使用产品主键加入了查询。 在加入之前,我已经测试了两个单独的查询,结果很好。只有1-2秒,我得到了结果。 但加入这两个人后,我最终还是等了3个小时。

SELECT
MPPFF.N_DX,
MPPFF.PM_A_P,
MPPFF.FEE1,
MPPFF.FEE2,
MPPFF.FEE3,
MPPFF.FEE4,
MPPFF.FEE11,
MPPFF.FEE12,
MPPFF.FEE5,
MPPFF.N_NO,
MPPFF.SETN_DX,
MPPFF.PRIME_NO,
MPPFF.SECN_NO,
MPPFF.COMM_A,
MPPFF.TYX_NO,
MPPFF.P_NAME,
MPPFF.B_BFX,
MPPFF.B_FM,
MPPFF.B_TO,
MPPFF.BB_NAME_P,
MPPFF.BB_NAME_S,
MPPFF.REVERSE_BFX,
MPPFF.TYX_REF_NO,
MPPFF.BB_NO_AX,
MPPFF.BB_NAME_AX,
MPPFF.DXC,
MPPFF.ST,
MPPFF.DAY,
MPPFF.CE_D_PRODUCT,
MPPFF.CE_H,
MPPFF.AS_C_E,
MPPFF.BCH,
MPPFF.RCPY_NO,
MPPFF.RE_BFX,
MPPFF.A_END,
MPPFF.PLACE,
MPPFF.MEMB_DX,
MPPFF.MBR_NO,
MPPFF.MBR_TR_BFX,
MPPFF.CE_D_TERM_CE,
MPPFF.MEMBER_AS,
MPPFF.C_USER,
MPPFF.C_BFX,
MPPFF.U_USER,
MPPFF.U_BFX

FROM (
        SELECT
        FF.N_DX,
        FF.PM_A_P,
        FF.FEE1,
        FF.FEE2,
        FF.FEE3,
        FF.FEE4,
        FF.FEE11,
        FF.FEE12,
        FF.FEE5,
        FF.N_NO,
        FF.SETN_DX,
        FF.PRIME_NO,
        FF.SECN_NO,
        FF.COMM_A,
        FF.TYX_NO,
        FF.P_NAME,
        FF.B_BFX,
        FF.B_FM,
        FF.B_TO,
        FF.BB_NAME_P,
        FF.BB_NAME_S,
        FF.REVERSE_BFX,
        FF.TYX_REF_NO,
        FF.BB_NO_AX,
        FF.BB_NAME_AX,
        FF.DXC,
        FF.ST,
        FF.DAY,
        FF.CE_D_PRODUCT,
        FF.CE_H,
        FF.AS_C_E,
        FF.RCPY_NO,
        FF.RE_BFX,
        FF.A_END,
        FF.BCH,
        MPP.MBR_NO,
        MPP.MBR_TR_BFX,
        MPP.CE_D_TERM_CE,
        MPP.C_USER,
        MPP.C_BFX,
        MPP.U_USER,
        MPP.U_BFX,
        MPP.PLACE,
        MPP.MEMBER_AS,
        MPP.TYX_DX,
        MPP.AS_DX,
        MPP.PRODUCT,
        MPP.POPL_DX,
        MPP.MEMB_DX,
        FF.TYX_DX

        FROM (
                SELECT
                MBR.MEMB_DX,
                MBR.MBR_NO,
                MBR.MBR_TR_BFX,
                MBR.CE_D_TERM_CE,
                MBR.C_USER,
                MBR.C_BFX,
                MBR.U_USER,
                MBR.U_BFX,
                MPP.PLACE,
                MPP.MEMBER_AS,
                MPP.TYX_DX,
                MPP.AS_DX,
                MPP.PRODUCT,
                MPP.POPL_DX

                FROM (
                        SELECT
                        MPP.PLACE,
                        MPP.MEMBER_AS,
                        MPP.TYX_DX,
                        MPP.AS_DX,
                        MPP.PRODUCT,
                        MPP.POPL_DX,
                        MMP.MEMB_DX

                        FROM(
                                SELECT
                                MPP.PLACE,
                                MPP.TYX_AS_DXC MEMBER_AS,
                                MPP.TYX_DX,
                                MPP.AS_DX,
                                MPP.POPL_DX,
                                RPT.PRODUCT

                                FROM
                                TABLE1 MPP

                                LEFT JOIN (
                                        SELECT
                                        SUBSTR(CE_D_PRODUCT,9) PRODUCT,
                                        AS_DX
                                        FROM
                                        TABLE6 RPT,
                                        TABLE7 PP
                                        WHERE
                                        PP.PRTY_DX = RPT.PRTY_DX
                                ) RPT
                                ON  MPP.AS_DX = RPT.AS_DX

                        ) MPP

                        LEFT JOIN (
                                SELECT
                                POPL_DX,
                                MEMB_DX
                                FROM
                                TABLE4
                        )MMP
                        ON MPP.POPL_DX=MMP.POPL_DX

                ) MPP,
                (
                        SELECT
                        MBR.MEMB_DX,
                        MBR.MBR_NO,
                        MBR.TERM_BFX MBR_TR_BFX,
                        MBR.CE_D_TERM_CE,
                        MBR.C_USER,
                        MBR.C_BFX,
                        MBR.U_USER,
                        MBR.U_BFX

                        FROM
                        TABLE8 MBR
                ) MBR
                WHERE
                MPP.MEMB_DX = MBR.MEMB_DX
        ) MPP
        INNER JOIN
        (
                SELECT
                FF.N_DX,
                ROUND(CB.FEE5 * FF.RATE,2) PM_A_P,
                CB.FEE1,
                CB.FEE2,
                CB.FEE3,
                CB.FEE4,
                CB.FEE11,
                CB.FEE12,
                CB.FEE5,
                FF.N_NO,
                FF.SETN_DX,
                FF.PRIME_NO,
                FF.SECN_NO,
                FF.COMM_A,
                FF.TYX_NO,
                FF.P_NAME_1||', '||FF.P_NAME_2||' '||FF.P_NAME_3 P_NAME,
                FF.B_BFX,
                FF.B_FM,
                FF.B_TO,
                FF.BB_NAME_1_P||', '||FF.BB_NAME_2_P BB_NAME_P,
                FF.BB_NAME_1_S||', '||FF.BB_NAME_2_S BB_NAME_S,
                CB.REVERSE_BFX,
                FF.TYX_REF_NO,
                FF.BB_NO_AX,
                FF.BB_NAME_1_AX||' '|| FF.BB_NAME_2_AX BB_NAME_AX,
                CASE 
                        WHEN FF.CE_D_ST IN ('A', 'B', 'C') THEN 'AC'
                        WHEN FF.DAY >1 THEN 'NEW'
                        ELSE 'AB'
                END DXC,
                FF.CE_D_ST ST,
                FF.DAY,
                FF.CE_D_PRODUCT,
                FF.CE_D_COMP CE_H,
                FF.AS_C AS_C_E,
                FF.RCPY_NO,
                FF.RE_BFX,
                ROUND(CB.A_S,2) A_END,
                FF.TYX_DX,
                MP.BCH

                FROM
                TABLE2 CB,
                TABLE3 FF

                LEFT JOIN (
                        SELECT
                        SUBSTR(CE_D_BCH_O,13) BCH,
                        TYX_DX
                        FROM
                        TABLE5 MP
                )MP
                ON MP.TYX_DX = FF.TYX_DX

                WHERE
                FF.SETN_DX = CB.SETN_DX AND
                EXTRACT( YEAR FROM FF.EFF_BFX) >=2013
        ) FF    
        ON MPP.TYX_DX = FF.TYX_DX

)MPPFF
;

4 个答案:

答案 0 :(得分:3)

使用ROWNUM阻止优化程序转换降低性能。

您遇到了一个常见问题 - 两个查询分别快速运行但在放在一起时运行缓慢。 Oracle不必按写入顺序运行查询。它可以合并视图,推送谓词,并且通常完全重写查询以按不同的顺序运行。通常这是一件好事,因为您不必担心连接表的物理顺序。但有时Oracle会应用错误的转换,结果也是灾难性的。

有两种方法可以解决这些问题。

  1. 查看表结构,语句,执行计划,SQL监控或跟踪,统计信息等。尝试找出哪些操作很慢,以及为什么(使用基数作为指导),然后尝试修复它。这个过程很容易花费数小时甚至数天,但这是最好的学习方式。
  2. 停止优化器将查询与简单技巧相结合。有几种方法可以做到这一点,但根据我的经验,最简单的方法是将伪列ROWNUM添加到您不想转换的任何内联视图中。 ROWNUM是一个特殊的列,它告诉Oracle“必须以特定的方式返回此查询块,不要对其执行任何操作”。
  3. 改变这个:

    --This is slow:
    select ...
    from
    (
        --This is fast:
        select ...
    ) inline_view1
    join
    (
        --This is fast:
        select ...
    ) inline_view2
        on ...
    

    到此:

    --Now this is fast.
    select ...
    from
    (
        --This is fast:
        select rownum /*add rownum to prevent slow transformations*/, ...
    ) inline_view1
    join
    (
        --This is fast:
        select rownum /*add rownum to prevent slow transformations*/, ...
    ) inline_view2
        on ...
    

    在您的代码中,我认为要修改的两个内联视图将是最外层MPPFF

    在旁注中,我不同意其他一些评论和答案。

    • CTE在这里没有帮助,因为没有一个表被使用过两次。
    • 您并不总是需要了解有关查询的百万个详细信息才能对其进行调整。除非你有时间并且想要提高你的技能。
    • 我认为你的整体查询结构很好。您正在构建出色的SQL语句的正确途径。内联视图是编写SQL的关键 - 构建小的代码单元,以简单的步骤组合它们,重复。将所有表放在一个大型连接中是意大利面条代码的一个配方。虽然我同意其他人的意见,但你应该避免使用老式的连接语法。并且查询将真正受益于一些评论和更有意义的名称。并且不要害怕将所有选择列表项放在一行上。拥有500列的行并不理想,但您希望专注于连接,而不是简单的列列表。

答案 1 :(得分:1)

由于所有嵌套,您的查询几乎无法读取。并且您将1992年之前的样式连接与当前连接语法混合在一起。不要使用过时的逗号分隔连接语法。它容易出错。所有外连接都是无效的,因为在某些时候,您将始终具有解除外连接记录的条件,例如在外连接table4的memb_dx上内连接table8时。

您的查询似乎转换为

select
  <several fields from the tables>
from table1 mpp
join table6 rpt on rpt.as_dx = mpp.as_dx
join table7 pp on pp.prty_dx = rpt.prty_dx
join table4 mmp on mmp.popl_dx = mpp.popl_dx
join table8 mbr on mpp.memb_dx = mmp.memb_dx
join table3 ff on ff.tyx_dx = mpp.tyx_dx and extract(year from ff.eff_bfx) >= 2013
join table2 cb on ff.setn_dx = cb.setn_dx
left join table5 mp on mp.tyx_dx = ff.tyx_dx;

也许你想要它

select
  <several fields from the tables>
from table1 mpp
left join table6 rpt on rpt.as_dx = mpp.as_dx
left join table7 pp on pp.prty_dx = rpt.prty_dx
left join table4 mmp on mmp.popl_dx = mpp.popl_dx
left join table8 mbr on mpp.memb_dx = mmp.memb_dx
join table3 ff on ff.tyx_dx = mpp.tyx_dx and extract(year from ff.eff_bfx) >= 2013
join table2 cb on ff.setn_dx = cb.setn_dx
left join table5 mp on mp.tyx_dx = ff.tyx_dx;

相反或类似的东西。摆脱所有的嵌套,并保持清晰易读的条款。

答案 2 :(得分:1)

其他人没有提到的是使用

EXTRACT( YEAR FROM FF.EFF_BFX) >=2013

这将EXTRACT函数应用于从TABLE3中选择的每一行(我相信这是查询中此FF所指的内容)。我建议用

替换上面的内容
FF.EFF_BFX >= TO_DATE('01-JAN-2013', 'DD-MON-YYYY')

或类似的东西。这只需要对TO_DATE进行一次调用即可生成日期常量,然后将其直接与FF.EFF_BFX进行比较,后者似乎是DATE类型的列。

此查询还对不同上下文中的不同实体多次使用相同的表别名(例如FFMPP等)。在我看来,这是不好的做法,我建议你重新设计查询,为每个实体使用一个唯一的别名,这将使查询更容易理解。

正如其他人所提到的,摆脱WHERE子句中的1992年之前的连接也有助于澄清正在发生的事情,以及摆脱长列列表。也可以删除一些子查询,这将使查询更清晰,更清晰。

在处理完所有上述内容后,我得到以下内容:

SELECT *
  FROM (SELECT *
          FROM TABLE1 MPP
          LEFT OUTER JOIN (SELECT SUBSTR(CE_D_PRODUCT, 9) PRODUCT,
                                  AS_DX
                             FROM TABLE6 RPT
                             INNER JOIN TABLE7 PP
                               ON PP.PRTY_DX = RPT.PRTY_DX) RPT
            ON MPP.AS_DX = RPT.AS_DX
          LEFT OUTER JOIN TABLE4 MMP
            ON MPP.POPL_DX = MMP.POPL_DX) MPP
  INNER JOIN TABLE8 MBR
    ON MPP.MEMB_DX = MBR.MEMB_DX
  INNER JOIN (SELECT FF.*,
                     CB.*,
                     ROUND(CB.FEE5 * FF.RATE,2) PM_A_P,
                     FF.P_NAME_1     || ', ' || FF.P_NAME_2 || ' ' || FF.P_NAME_3 P_NAME,
                     FF.BB_NAME_1_P  || ', ' || FF.BB_NAME_2_P BB_NAME_P,
                     FF.BB_NAME_1_S  || ', ' || FF.BB_NAME_2_S BB_NAME_S,
                     FF.BB_NAME_1_AX || ' '  || FF.BB_NAME_2_AX BB_NAME_AX,
                     CASE 
                       WHEN FF.CE_D_ST IN ('A', 'B', 'C') THEN 'AC'
                       WHEN FF.DAY > 1 THEN 'NEW'
                       ELSE 'AB'
                     END DXC,
                     ROUND(CB.A_S,2) A_END,
                     SUBSTR(MP.CE_D_BCH_O, 13) AS BCH
                FROM TABLE2 CB
                INNER JOIN TABLE3 FF
                  ON FF.SETN_DX = CB.SETN_DX
                LEFT OUTER JOIN TABLE5 MP
                  ON MP.TYX_DX = FF.TYX_DX
                WHERE FF.EFF_BFX >= TO_DATE('01-JAN-2013', 'DD-MON-YYYY')) FF
    ON MPP.TYX_DX = FF.TYX_DX

祝你好运。

答案 3 :(得分:0)

我试图让您的查询更具可读性:

SELECT MPPFF.*
FROM 
    (SELECT FF.*, MPP.*
    FROM 
        (SELECT MBR.*, MPP.*
        FROM 
            (SELECT MPP.*, MMP.*
            FROM 
                (SELECT MPP.*, RPT.*
                FROM TABLE1 MPP
                LEFT JOIN (SELECT * FROM TABLE6 RPT, TABLE7 PP WHERE PP.PRTY_DX = RPT.PRTY_DX) RPT ON  MPP.AS_DX = RPT.AS_DX) MPP
            LEFT JOIN (SELECT * FROM TABLE4) MMP ON MPP.POPL_DX=MMP.POPL_DX) MPP, 
            (SELECT MBR.* FROM TABLE8 MBR) MBR
        WHERE MPP.MEMB_DX = MBR.MEMB_DX) MPP
    INNER JOIN (SELECT FF.*, CB.* FROM TABLE2 CB, TABLE3 FF
        LEFT JOIN (SELECT * FROM TABLE5 MP ) MP ON MP.TYX_DX = FF.TYX_DX
    WHERE FF.SETN_DX = CB.SETN_DX 
        AND EXTRACT( YEAR FROM FF.EFF_BFX) >=2013) FF ON MPP.TYX_DX = FF.TYX_DX) MPPFF
;

您选择了8个不同的表,唯一的WHERE条件是EXTRACT( YEAR FROM FF.EFF_BFX) >= 2013

除非表格很小,否则总是需要一些时间来查询它们。

为什么要混用ANSI连接语法和旧式Oracle连接语法?