一个项目碰到性能问题,要我去优化。Oracle数据库,项目日志记录一个查询语句运行4个小时。因为对Oracle DB管理调优不熟,一开始从逻辑层面优化SQL语句,对照执行计划觉得应当差不多了,测试运行时间比较长(等了1分钟多没有运行完,就Cancel掉)。 进一步的分析发现:

    1. PL/SQL Developer的Explain Plan Window中执行计划显示不准确。

    可能是SQL语句比较复杂,加了Hints,做了某些修改之后重新执行,PL/SQL只是将之前缓存的执行计划调了出来。如果发现其显示的执行计划不准确,可以新开一个Explain Plan Window重新执行,这样显示的执行计划是准确的。

    2. 某些情况下Oracle选择索引有问题。

    发现这种情况后通过Hint指定索引。

    3. 有时Oracle对Join方法的选择不准确。

   Oracle特别喜欢使用NESTED LOOPS,这种Join方法有其适用的前提条件,例如数据量很大、NESTED LOOPS循环次数很多、没有高效的索引等,都会导致NESTED LOOPS效率急剧下降。因此适当的使用MERGE JOIN、HASH JOIN对效率的提升将相当明显。

    使用Hint指定Join方法。


    远程登录到项目的正式环境测试,初步优化调整之后,语句的执行时间为10秒钟。

    另外老观点:应用中不要用复杂的SQL,从业务分析设计、表结构设计上避免。


    优化前的SQL:

Select MRDemand.TranCode,MRDemand.CompanyCode,MRDemand.PlantCode,

       MRDemand.ItemCode,MRDemand.DemandDate,MRDemand.VendorCode,MRDemand.DemandQty,

       CASE WHEN (MRDemand.AdjustQty-NVL(ViewTow.SHIPQTY,0))<0 THEN 0  

                ELSE (MRDemand.AdjustQty-NVL(ViewTow.SHIPQTY,0)) END AdjustQty, 

       MRDemand.PreDemandQty,MRDemand.PreAdjustQty,

       MRDemand.MRType,MRDemand.PlanUGCode,MRDemand.PurchUGCode,MRDemand.OverDueQty, 

       MRDemand.CreateUser,MRDemand.CreateDate,MRDemand.CreateTime,

       MRDemand.LogUser,MRDemand.LogDate,MRDemand.LogTime, 

       MRDemand.Addition1,MRDemand.Addition2 

From MRDemand  

Inner Join ( 

            Select PI.CompanyCode,PI.PlantCode,PI.ItemCode  

            From Plant2Item PI,ChianTypeDef CTD  

            Where PI.CompanyCode=CTD.CompanyCode And PI.PlantCode=CTD.PlantCode 

                  And PI.ChianSubType=CTD.ChianSubType And CTD.CHIANTYPE<>'Indirect' 

    ) ViewOne 

    On MRDemand.CompanyCode=ViewOne.CompanyCode And MRDemand.PlantCode=ViewOne.PlantCode

            And MRDemand.ItemCode=ViewOne.ItemCode 

Inner Join ( 

            Select Distinct a.CompanyCode,a.PlantCode,a.ItemCode 

            From (

                 Select Distinct CompanyCode, PlantCode, ItemCode 

                 From MRDemand 

                 Where DemandDate>=to_number(to_char(Sysdate,'YYYYMMDD')) And AdjustQty>0 

                       And VendorCode='*' And PlantCode In ('','2000')) d 

            Inner Join PlantItem2Vendor a On d.CompanyCode=a.CompanyCode

                           And d.PlantCode=a.PlantCode And d.ItemCode=a.ItemCode 

            Inner Join PlantItemVAssign b On a.CompanyCode=b.CompanyCode

                           And a.PlantCode=b.PlantCode And a.ItemCode=b.ItemCode 

            Inner Join PlantItemVAssignDetail c On c.AssignCode=b.AssignCode And c.VendorCode=a.VendorCode 

            Where b.InvalidDate>=to_number(to_char(Sysdate,'YYYYMMDD')) 

    ) View3 On MRDemand.CompanyCode=View3.CompanyCode And MRDemand.PlantCode=View3.PlantCode

                     And MRDemand.ItemCode=View3.ItemCode 

Left Join ( 

            Select ASN.COMPANYCODE,ASN.PLANTCODE,ASNDetail.ITEMCODE,20070321 PLANDATE,

                     Sum(ASNDetail.SHIPQTY) SHIPQTY  

            From ASN,ASNDetail 

            Where ASN.STNo=ASNDetail.STNo And ASN.STTYPE='JIT'  

                And ASN.STStatus In ('Release','WaitCheck') 

                And ASNDetail.STDSTATUS In ('Release','WaitCheck')

                And ASNDetail.CHECKSTATUS Not In ('Qualified','UnQualified')  

                And ASNDetail.SSDate>19010101 And ASNDetail.SSDate<20070321 

            Group By ASN.COMPANYCODE,ASN.PLANTCODE,ASNDetail.ITEMCODE 

    ) ViewTow 

    On MRDemand.CompanyCode=ViewTow.CompanyCode And MRDemand.PlantCode=ViewTow.PlantCode  

        And MRDemand.ItemCode=ViewTow.ItemCode And MRDemand.DemandDate=ViewTow.PLANDATE 

Where MRDemand.AdjustQty>0 And MRDemand.VendorCode='*'

      And MRDemand.DemandDate Between :V00001 And :V00002 

      and MRDemand.PlantCode in ('','2000') 

Union 

Select MRDemand.CompanyCode,MRDemand.PlantCode,MRDemand.ItemCode,MRDemand.DemandDate,

          MRDemand.VendorCode,MRDemand.TranCode,

          MRDemand.DemandQty,MRDemand.AdjustQty,MRDemand.PreDemandQty,MRDemand.PreAdjustQty,

          MRDemand.MRType,MRDemand.PlanUGCode, 

          MRDemand.PurchUGCode,MRDemand.OverDueQty,

          MRDemand.CreateUser,MRDemand.CreateDate,MRDemand.CreateTime,

          MRDemand.LogUser, MRDemand.LogDate,MRDemand.LogTime,MRDemand.Addition1,MRDemand.Addition2 

From MRDemand  

Inner Join ( 

            Select PI.CompanyCode,PI.PlantCode,PI.ItemCode  

            From Plant2Item PI,ChianTypeDef CTD  

            Where PI.CompanyCode=CTD.CompanyCode And PI.PlantCode=CTD.PlantCode 

                And PI.ChianSubType=CTD.ChianSubType And CTD.CHIANTYPE<>'Indirect' 

    ) ViewOne 

    On MRDemand.CompanyCode=ViewOne.CompanyCode And MRDemand.PlantCode=ViewOne.PlantCode

         And MRDemand.ItemCode=ViewOne.ItemCode     

Inner Join PlantItem2Vendor On MRDemand.CompanyCode=PlantItem2Vendor.CompanyCode 

      And MRDemand.PlantCode=PlantItem2Vendor.PlantCode  And MRDemand.ItemCode=PlantItem2Vendor.ItemCode  

      And MRDemand.VendorCode=PlantItem2Vendor.VendorCode 

Where MRDemand.AdjustQty>0 And MRDemand.VendorCode<>'*' And MRDemand.DemandDate Between :V00003 

      And :V00004 and MRDemand.PlantCode in ('','2000')

    优化前执行计划:

   


    优化后的测试SQL:

Select /*+ ordered use_hash(t3 d) use_hash(t3 t2) */

       d.CompanyCode,d.PlantCode,d.ItemCode,d.DemandDate,d.VendorCode,d.TranCode,d.DemandQty,

       Case When (d.AdjustQty-NVL(t2.ShipQty,0))<0 Then 0 Else (d.AdjustQty-NVL(t2.ShipQty,0)) End AdjustQty,

       d.PreDemandQty,d.PreAdjustQty,d.MRType,d.PlanUGCode,d.PurchUGCode,d.OverDueQty,

       d.CreateUser,d.CreateDate,d.CreateTime,d.LogUser,d.LogDate,d.LogTime,

       d.Addition1,d.Addition2

From (

     Select Distinct t1.*

     From (Select Distinct CompanyCode, PlantCode, ItemCode

          From MRDemand

          Where DemandDate>=20070322 and PlantCode in ('2000') And VendorCode='*' And AdjustQty>0) t1 

    Inner Join Plant2Item pi On pi.CompanyCode=t1.CompanyCode And pi.PlantCode=t1.PlantCode And pi.ItemCode=t1.ItemCode

    Inner Join ChianTypeDef ct On ct.CompanyCode=pi.CompanyCode 

          And ct.PlantCode=pi.PlantCode And ct.ChianSubType=pi.ChianSubType

          And ct.ChianType In ('Self','Direct') And ct.PlantCode In ('2000')

    Inner Join PlantItem2Vendor vi On vi.CompanyCode=t1.CompanyCode And vi.PlantCode=t1.PlantCode 

          And vi.ItemCode=t1.ItemCode

    Inner Join PlantItemVAssign via On via.CompanyCode=t1.CompanyCode And via.PlantCode=t1.PlantCode 

          And via.ItemCode=t1.ItemCode And via.InvalidDate>=20070322

    Inner Join PlantItemVAssignDetail viad On viad.AssignCode=via.AssignCode And viad.VendorCode=vi.VendorCode

) t3 

Inner Join MRDemand d On d.CompanyCode=t3.CompanyCode And d.PlantCode=t3.PlantCode And d.ItemCode=t3.ItemCode

Left Join(Select /*+ ordered index(snd PK_ASNDETAIL) */

                      sn.CompanyCode,sn.PlantCode,snd.ItemCode,20070322 As PlanDate,Sum(snd.ShipQty) As ShipQty

              From ASN sn

              Inner Join ASNDetail snd On sn.STNO=snd.STNO

              Where sn.PlantCode In ('2000') And sn.STStatus In ('Release','WaitCheck') And sn.STType='JIT'

                   And snd.STDStatus In ('Release','WaitCheck') And snd.CheckStatus Not In ('Qualified','UnQualified')

                   And snd.SSDate>20070222 And snd.SSDate<20070322

              Group By sn.CompanyCode,sn.PlantCode,snd.ItemCode

) t2 On t2.CompanyCode=d.CompanyCode And t2.PlantCode=d.PlantCode

           And t2.ItemCode=d.ItemCode And t2.PlanDate=d.DemandDate

Where d.DemandDate>=20070322 and d.PlantCode in ('2000') And d.VendorCode='*' And d.AdjustQty>0

Union All

Select /*+ ordered use_hash(t1 d) */

       d.CompanyCode,d.PlantCode,d.ItemCode,d.DemandDate,d.VendorCode,d.TranCode,

       d.DemandQty,d.AdjustQty,d.PreDemandQty,d.PreAdjustQty,d.MRType,d.PlanUGCode,

       d.PurchUGCode,d.OverDueQty,d.CreateUser,d.CreateDate,d.CreateTime,d.LogUser,

       d.LogDate,d.LogTime,d.Addition1,d.Addition2

From

(

    Select Distinct PlantCode, CompanyCode, ItemCode, VendorCode

    From MRDemand

    Where DemandDate>=20070322 And PlantCode in ('2000') And VendorCode<>'*' And AdjustQty>0

) t1

Inner Join PlantItem2Vendor vi On vi.PlantCode=t1.PlantCode And vi.CompanyCode=t1.CompanyCode 

      And vi.ItemCode=t1.ItemCode And vi.VendorCode=t1.VendorCode

Inner Join Plant2Item pi

      On pi.CompanyCode=t1.CompanyCode And pi.PlantCode=t1.PlantCode And pi.ItemCode=t1.ItemCode

Inner Join (

        Select Distinct CompanyCode, PlantCode, ChianSubType 

        From ChianTypeDef 

        Where ChianType In ('Self','Direct') And PlantCode In ('2000')

      ) ct On ct.CompanyCode=pi.CompanyCode And ct.PlantCode=pi.PlantCode And ct.ChianSubType=pi.ChianSubType

Inner Join MRDemand d On d.CompanyCode=t1.CompanyCode And d.PlantCode=t1.PlantCode 

      And d.ItemCode=t1.ItemCode And d.VendorCode=t1.VendorCode

Where d.DemandDate>=20070322 and d.PlantCode in ('2000') And d.VendorCode<>'*' And d.AdjustQty>0

    优化后的执行计划:

   

    执行计划中收缩起来的部分,基本都是INDEX UNIQUE SCAN,其它部分INDEX RANGE SCAN的,可以确定扫描的范围很小,保证INDEX的使用效率很高。