记一次SQL优化过程

环境:Microsoft SQL Server 2016 (SP2-CU3)企业版

问题SQL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
select 
    RowNumber = ROW_NUMBER() OVER 
    
        -- This ordering is from the various Fulfillment Map sort orders to match the fulfillment app's row order.
        ORDER BY 
            htly.LicenseYear, 
            mht.Name
            h.HuntFirstOpenDate, 
            h.DisplayOrder,
            h.HuntCode,
            ci_orderby.LastName,
            ci_orderby.FirstName,
            fmu.FulfillmentMailingUnitID
    ),
    ShippingName = ISNULL(fism_aot.ShippingName,  dbo.udf_GetCustomerName(c.CustomerID)),
    FulfillmentMailingUnitID = fmu.FulfillmentMailingUnitID,
    GoID = goid.IdentityValue,
    MailingZip = ISNULL(fism_zc.ZipCode, zc.ZipCode),
    TransactionID = fism_th.TransactionID,
    TransactionHeaderID = fism_th.TransactionHeaderID,
    HuntDate = h.HuntFirstOpenDate,
    HuntCode = h.HuntCode,
    -- Header info
    BatchNumber = fmulg.FulfillmentMailingUnitLockGroupID,
    PrintedByUserName = au.UserName,
    LockedDate = fmulg.LockedDate
from
    dbo.FulfillmentMailingUnitLockGroup fmulg
    cross join dbo.Enum_IdentityType eit
    cross join dbo.Enum_LicenseActionType elat
    inner join dbo.FulfillmentMailingUnitLock fmul
        on fmulg.FulfillmentMailingUnitLockGroupID = fmul.FulfillmentMailingUnitLockGroupID
    inner join dbo.FulfillmentMailingUnit fmu
        on fmul.LockedFulfillmentMailingUnitID = fmu.FulfillmentMailingUnitID
    inner join dbo.ApplicationUser au
        on fmulg.LockedByApplicationUserID = au.ApplicationUserID
    -- Getting to the Transaction Header by FulfillmentInternetSalesMap OR FulfillmentDrawIssuanceMap
    left join dbo.FulfillmentInternetSalesMap fism
        on fmu.FulfillmentMailingUnitID = fism.FulfillmentMailingUnitID
    left join dbo.FulfillmentDrawIssuanceMap fdim
        on fmu.FulfillmentMailingUnitID = fdim.FulfillmentMailingUnitID
        left join dbo.TransactionHeader th  
            on fism.TransactionHeaderID = th.TransactionHeaderID
                or fdim.TransactionHeaderID = th.TransactionHeaderID
    left join dbo.TransactionHeader fdim_th
        on fdim.TransactionHeaderID = fdim_th.TransactionHeaderID
    -- Getting to License from FulfillmentDrawNotificationMap
    left join dbo.FulfillmentDrawNotificationMap fdnm
        on fmu.FulfillmentMailingUnitID = fdnm.FulfillmentMailingUnitID
        left join dbo.DrawTicketLicense fdnm_dtl
            on fdnm.DrawTicketLicenseID = fdnm_dtl.DrawTicketLicenseID 
        left join dbo.License fdnm_l
            on fdnm_dtl.LicenseID = fdnm_l.LicenseID
        left join dbo.DrawTicket fdnm_dt
            on fdnm_dtl.DrawTicketID = fdnm_dt.DrawTicketID
        left join dbo.DrawTicketHuntChoice fdnm_dthc
            on 
                fdnm_dt.DrawTicketID = fdnm_dthc.DrawTicketID
                and
                (
                    -- If the draw ticket is a winner, link to the hunt choice that won.
                    (fdnm_dt.WasDrawn = 1 and fdnm_dthc.WasDrawn = 1)
                    -- Else if the draw ticket was not a winner, link to the first hunt choice since
                    -- Losing and Alternate notifications are not valid for multi-choice hunts
                    or (fdnm_dt.WasDrawn = 0 and fdnm_dthc.OrderIndex = 1)
                )
        left join dbo.TransactionDetail fdim_td
            on fdim.TransactionHeaderID = fdim_td.TransactionHeaderID
        left join dbo.LicenseAction fdim_la
            on fdim_td.TransactionDetailID = fdim_la.TransactionDetailID
                -- This might be silly since it should only be Issued for issuance... (currently it's sold in the stored proc that issues tags)
                and (fdim_la.LicenseActionTypeID = elat.Sold or fdim_la.LicenseActionTypeID = elat.Issued or fdim_la.LicenseActionTypeID = elat.Duplicated)
        left join dbo.License fdim_l
            on fdim_la.LicenseID = fdim_l.LicenseID
         
        left join dbo.Hunt h
            on fdnm_dthc.HuntID = h.HuntID
                or fdim_l.HuntID = h.HuntID
        left join dbo.HuntTypeLicenseYear htly
            on h.HuntTypeLicenseYearID = htly.HuntTypeLicenseYearID
        left join dbo.MasterHuntType mht
            on htly.MasterHuntTypeID = mht.MasterHuntTypeID
    left join dbo.Customer c
        on fdnm_l.CustomerID = c.CustomerID
            or th.CustomerID = c.CustomerID
    left join dbo.CustomerIndividual ci
        on c.CustomerID = ci.CustomerID
    left join dbo.CustomerIdentity goid
        on c.CustomerID = goid.CustomerID
            and goid.IdentityTypeID = eit.GOID
            and goid.[Status] = 1
    left join dbo.AddressDetail ad
        on c.MailingAddressID = ad.AddressID
            and ad.IsActive = 1
    left join dbo.ZipCode zc
        on ad.ZipCodeID = zc.ZipCodeID
    left join dbo.CustomerIndividual ci_orderby
        on fdnm_l.CustomerID = ci_orderby.CustomerID
            or fdim_th.CustomerID = ci_orderby.CustomerID
     
    left join dbo.TransactionHeader fism_th 
        on fism.TransactionHeaderID = fism_th.TransactionHeaderID
    left join dbo.ActiveOutdoorsTransaction fism_aot
        on fism_aot.TransactionID = fism_th.TransactionID
    left join dbo.AddressDetail fism_ad 
        on fism_aot.ShippingAddressID = fism_ad.AddressID
            and fism_ad.IsActive = 1
    left join dbo.ZipCode fism_zc
        on fism_ad.ZipCodeID = fism_zc.ZipCodeID
    where
        fmulg.FulfillmentMailingUnitLockGroupID = @FulfillmentMailingUnitLockGroupID

该SQL执行192s后出记录,分析一下sql的执行计划:

分析一:

最终的排序消耗了大量的cost:

分析二:

该SQL存在大量多表连接,MSSQL引擎由于统计信息的算法单一,在处理大量级联连接时,实际数据可能严重偏离统计信息

连接中存在Actual Rows和Estimated Rows严重不一致的情况,随着连接表数目增加,该不一致更加严重:

经过分析,优化的目标是减少多表连接的统计信息不一致导致的执行计划错误并且对最终的排序操作进行外推。

优化的手法主要是利用临时表固化统计信息,外推排序:

最终优化SQL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
select 
    fmu.FulfillmentMailingUnitID
    ,elat.Sold
    ,elat.Issued
    ,elat.Duplicated
    ,fmulg.FulfillmentMailingUnitLockGroupID
    ,au.UserName
    ,fmulg.LockedDate
    ,eit.GOID
    into #temp
    from 
    dbo.FulfillmentMailingUnitLockGroup fmulg
    cross join dbo.Enum_IdentityType eit
    cross join dbo.Enum_LicenseActionType elat
    inner join dbo.FulfillmentMailingUnitLock fmul
        on fmulg.FulfillmentMailingUnitLockGroupID = fmul.FulfillmentMailingUnitLockGroupID
    inner join dbo.FulfillmentMailingUnit fmu
        on fmul.LockedFulfillmentMailingUnitID = fmu.FulfillmentMailingUnitID
    inner join dbo.ApplicationUser au
        on fmulg.LockedByApplicationUserID = au.ApplicationUserID
    where
        fmulg.FulfillmentMailingUnitLockGroupID = @FulfillmentMailingUnitLockGroupID
    select 
        fdnm_l.CustomerID fdnm_l_CustomerID,
        th.CustomerID th_CustomerID,
        fdim_th.CustomerID fdim_th_CustomerID,
        t.FulfillmentMailingUnitID,
        h.HuntFirstOpenDate,
        h.HuntCode,
        t.FulfillmentMailingUnitLockGroupID,
        t.UserName,
        LockedDate,
        t.GOID,
        htly.LicenseYear, 
        mht.Name
        h.DisplayOrder,
        --ci_orderby.LastName,
        --ci_orderby.FirstName,
        fism.TransactionHeaderID
        into #temp1
        from #temp t
     
        -- Getting to the Transaction Header by FulfillmentInternetSalesMap OR FulfillmentDrawIssuanceMap
        left join dbo.FulfillmentInternetSalesMap fism
            on t.FulfillmentMailingUnitID = fism.FulfillmentMailingUnitID
        left join dbo.FulfillmentDrawIssuanceMap fdim
            on t.FulfillmentMailingUnitID = fdim.FulfillmentMailingUnitID
        left join dbo.TransactionHeader th  
            on fism.TransactionHeaderID = th.TransactionHeaderID
                or fdim.TransactionHeaderID = th.TransactionHeaderID
        left join dbo.TransactionHeader fdim_th
            on fdim.TransactionHeaderID = fdim_th.TransactionHeaderID
        -- Getting to License from FulfillmentDrawNotificationMap
        left join dbo.FulfillmentDrawNotificationMap fdnm
            on t.FulfillmentMailingUnitID = fdnm.FulfillmentMailingUnitID
        left join dbo.DrawTicketLicense fdnm_dtl
            on fdnm.DrawTicketLicenseID = fdnm_dtl.DrawTicketLicenseID 
        left join dbo.License fdnm_l
            on fdnm_dtl.LicenseID = fdnm_l.LicenseID
        left join dbo.DrawTicket fdnm_dt
            on fdnm_dtl.DrawTicketID = fdnm_dt.DrawTicketID
        left join dbo.DrawTicketHuntChoice fdnm_dthc
            on 
                fdnm_dt.DrawTicketID = fdnm_dthc.DrawTicketID
                and
                (
                    -- If the draw ticket is a winner, link to the hunt choice that won.
                    (fdnm_dt.WasDrawn = 1 and fdnm_dthc.WasDrawn = 1)
                    -- Else if the draw ticket was not a winner, link to the first hunt choice since
                    -- Losing and Alternate notifications are not valid for multi-choice hunts
                    or (fdnm_dt.WasDrawn = 0 and fdnm_dthc.OrderIndex = 1)
                )
        left join dbo.TransactionDetail fdim_td
            on fdim.TransactionHeaderID = fdim_td.TransactionHeaderID
        left join dbo.LicenseAction fdim_la
            on fdim_td.TransactionDetailID = fdim_la.TransactionDetailID
                -- This might be silly since it should only be Issued for issuance... (currently it's sold in the stored proc that issues tags)
                and (fdim_la.LicenseActionTypeID = t.Sold or fdim_la.LicenseActionTypeID = t.Issued or fdim_la.LicenseActionTypeID = t.Duplicated)
        left join dbo.License fdim_l
            on fdim_la.LicenseID = fdim_l.LicenseID
     
        left join dbo.Hunt h
            on fdnm_dthc.HuntID = h.HuntID
                or fdim_l.HuntID = h.HuntID
        left join dbo.HuntTypeLicenseYear htly
            on h.HuntTypeLicenseYearID = htly.HuntTypeLicenseYearID
        left join dbo.MasterHuntType mht
            on htly.MasterHuntTypeID = mht.MasterHuntTypeID
    --set statistics io on
    --set statistics time on
    select 
        t1.LicenseYear,
        t1.Name
        t1.DisplayOrder,
        c.CustomerID,
        t1.FulfillmentMailingUnitID,
        t1.GOID,
        zc.ZipCode,
        t1.HuntFirstOpenDate,
        t1.HuntCode,
        t1.FulfillmentMailingUnitLockGroupID,
        t1.UserName,
        t1.LockedDate,
        t1.fdnm_l_CustomerID,
        t1.fdim_th_CustomerID,
        t1.TransactionHeaderID
     into #temp2
     from #temp1 t1
     
        -- Getting to Cusotmer from the joined transaction header or the license from the DrawTicketLicense
        left join dbo.Customer c
            on t1.fdnm_l_CustomerID = c.CustomerID
                or t1.th_CustomerID = c.CustomerID
        left join dbo.CustomerIndividual ci
            on c.CustomerID = ci.CustomerID
         
        left join dbo.AddressDetail ad
            on c.MailingAddressID = ad.AddressID
                and ad.IsActive = 1
        left join dbo.ZipCode zc
            on ad.ZipCodeID = zc.ZipCodeID
         
    select 
        t2.LicenseYear,
        t2.Name
        t2.DisplayOrder,
        ci_orderby.LastName,
        ci_orderby.FirstName,
        ShippingName = ISNULL(fism_aot.ShippingName,  dbo.udf_GetCustomerName(t2.CustomerID)),
        FulfillmentMailingUnitID = t2.FulfillmentMailingUnitID,
        GoID = goid.IdentityValue,
        MailingZip = ISNULL(fism_zc.ZipCode, t2.ZipCode),
        TransactionID = fism_th.TransactionID,
        TransactionHeaderID = fism_th.TransactionHeaderID,
        HuntDate = t2.HuntFirstOpenDate,
        HuntCode = t2.HuntCode,
        -- Header info
        BatchNumber = t2.FulfillmentMailingUnitLockGroupID,
        PrintedByUserName = t2.UserName,
        LockedDate = t2.LockedDate 
    into #temp3
    from #temp2 t2
            left join dbo.CustomerIdentity goid
                on t2.CustomerID = goid.CustomerID
                and goid.IdentityTypeID = t2.GOID 
                and goid.[Status] = 1
            left join dbo.CustomerIndividual ci_orderby
                on t2.fdnm_l_CustomerID = ci_orderby.CustomerID
                    or t2.fdim_th_CustomerID = ci_orderby.CustomerID
     
            left join dbo.TransactionHeader fism_th 
                on t2.TransactionHeaderID = fism_th.TransactionHeaderID
            left join dbo.ActiveOutdoorsTransaction fism_aot
                on fism_aot.TransactionID = fism_th.TransactionID
            left join dbo.AddressDetail fism_ad 
                on fism_aot.ShippingAddressID = fism_ad.AddressID
                    and fism_ad.IsActive = 1
            left join dbo.ZipCode fism_zc
                on fism_ad.ZipCodeID = fism_zc.ZipCodeID
    select  RowNumber = ROW_NUMBER() OVER 
        
            -- This ordering is from the various Fulfillment Map sort orders to match the fulfillment app's row order.
            ORDER BY 
                t3.LicenseYear, 
                t3.Name
                t3.HuntDate, 
                t3.DisplayOrder,
                t3.HuntCode,
                t3.LastName,
                t3.FirstName,
                t3.FulfillmentMailingUnitID
        ),
        ShippingName,
        FulfillmentMailingUnitID,
        GoID,
        MailingZip,
        TransactionID,
        TransactionHeaderID,
        HuntDate,
        HuntCode,
        -- Header info
        BatchNumber,
        PrintedByUserName,
        LockedDate
    from #temp3 t3
    drop table #temp
    drop table #temp1
    drop table #temp2
    drop table #temp3

经过测试,执行时间由192秒降低到2秒。

原文地址:https://www.cnblogs.com/database/p/11649239.html