Bu sorgu bir TVF'ye sarıldığında neden önemli ölçüde yavaşlıyor?


17

Sadece birkaç saniye içinde kendi başına çalışan oldukça karmaşık bir sorgu var, ama tablo değerli bir işlev sarılmış, çok daha yavaş; Aslında bitmesine izin vermedim, ama bitmeden on dakikaya kadar sürüyor. Tek değişiklik, iki tarih değişkeninin (tarih değişmezleriyle başlatılan) tarih parametreleriyle değiştirilmesidir:

Yedi Saniyede Koşu

DECLARE @StartDate DATE = '2011-05-21'
DECLARE @EndDate   DATE = '2011-05-23'

DECLARE @Data TABLE (...)
INSERT INTO @Data(...) SELECT...

SELECT * FROM @Data

En Az On Dakika Koşusu

CREATE FUNCTION X (@StartDate DATE, @EndDate DATE)
  RETURNS TABLE AS RETURN
  SELECT ...

SELECT * FROM X ('2011-05-21', '2011-05-23')

Daha önce işlevi bir RETURNS @Data TABLE (...) yan tümcesi ile çok deyimli bir TVF olarak yazmıştım, ancak satır içi yapısı için fark edilir bir değişiklik yapmadı. TVF'nin uzun çalışma süresi gerçek SELECT * FROM Xzamandır; aslında UDF oluşturmak sadece birkaç saniye sürer.

Söz konusu sorguyu gönderebilirim, ancak biraz uzun (~ 165 satır) ve ilk yaklaşımın başarısına dayanarak, başka bir şeyin devam ettiğinden şüpheleniyorum. İcra planlarına göz gezdirirken, aynı görünüyorlar.

Ben değişiklik olmadan, daha küçük bölümlere sorgu kırma denedim. Tek bir bölüm tek başına yürütüldüğünde birkaç saniyeden fazla sürmez, ancak TVF hala askıdadır.

Çok benzer bir soru görüyorum, /programming/4190506/sql-server-2005-table-valued-function-weird-performance , ancak çözümün geçerli olduğundan emin değilim. Belki birisi bu problemi gördü ve daha genel bir çözüm biliyor mu? Teşekkürler!

Birkaç dakika işlendikten sonra dm_exec_requests:

session_id              59
request_id              0
start_time              40688.46517
status                  running
command                 UPDATE
sql_handle              0x030015002D21AF39242A1101ED9E00000000000000000000
statement_start_offset  10962
statement_end_offset    16012
plan_handle             0x050015002D21AF3940C1E6B0040000000000000000000000
database_id                 21
user_id                 1
connection_id           314AE0E4-A1FB-4602-BF40-02D857BAD6CF
blocking_session_id         0
wait_type               NULL
wait_time                   0
last_wait_type          SOS_SCHEDULER_YIELD
wait_resource   
open_transaction_count  0
open_resultset_count    1
transaction_id              48030651
context_info            0x
percent_complete        0
estimated_completion_time   0
cpu_time                    344777
total_elapsed_time          348632
scheduler_id            7
task_address            0x000000045FC85048
reads                   1549
writes                  13
logical_reads           30331425
text_size               2147483647
language                us_english
date_format             mdy
date_first              7
quoted_identifier           1
arithabort              1
ansi_null_dflt_on       1
ansi_defaults           0
ansi_warnings           1
ansi_padding            1
ansi_nulls                  1
concat_null_yields_null 1
transaction_isolation_level 2
lock_timeout            -1
deadlock_priority           0
row_count                   105
prev_error              0
nest_level              1
granted_query_memory    170
executing_managed_code  0
group_id                2
query_hash              0xBE6A286546AF62FC
query_plan_hash         0xD07630B947043AF0

Sorgunun tamamı şöyledir:

CREATE FUNCTION Routine.MarketingDashboardECommerceBase (@StartDate DATE, @EndDate DATE)
RETURNS TABLE AS RETURN
    WITH RegionsByCode AS (SELECT CountryCode, MIN(Region) AS Region FROM Staging.Volusion.MarketingRegions GROUP BY CountryCode)
        SELECT
            D.Date, Div.Division, Region.Region, C.Category1, C.Category2, C.Category3,
            COALESCE(V.Visits,          0) AS Visits,
            COALESCE(Dem.Demos,         0) AS Demos,
            COALESCE(S.GrossStores,     0) AS GrossStores,
            COALESCE(S.PaidStores,      0) AS PaidStores,
            COALESCE(S.NetStores,       0) AS NetStores,
            COALESCE(S.StoresActiveNow, 0) AS StoresActiveNow
            -- This line causes the run time to climb from a few seconds to over an hour!
            --COALESCE(V.Visits,          0) * COALESCE(ACS.AvgClickCost, GAAC.AvgAdCost, 0.00) AS TotalAdCost
            -- This line alone does not inflate the run time
            --ACS.AvgClickCost
            -- This line is enough to increase the run time to at least a couple minutes
            --GAAC.AvgAdCost
        FROM
            --Dates AS D
            (SELECT SQLDate AS Date FROM Dates WHERE SQLDate BETWEEN @StartDate AND @EndDate) AS D
            CROSS JOIN (SELECT 'UK' AS Division UNION SELECT 'US' UNION SELECT 'IN' UNION SELECT 'Unknown') AS Div
            CROSS JOIN (SELECT Category1, Category2, Category3 FROM Routine.MarketingDashboardCampaignMap UNION SELECT 'Unknown', 'Unknown', 'Unknown') AS C
            CROSS JOIN (SELECT DISTINCT Region FROM Staging.Volusion.MarketingRegions) AS Region
            -- Visitors
            LEFT JOIN
                (
                SELECT
                    V.Date,
                    CASE    WHEN V.Country IN ('United Kingdom', 'Guernsey', 'Ireland', 'Jersey') THEN 'UK'
                        WHEN V.Country IN ('United States', 'Canada', 'Puerto Rico', 'U.S. Virgin Islands') THEN 'US'
                        ELSE 'IN' END AS Division,
                    COALESCE(MR.Region, 'Unknown') AS Region,
                    C.Category1, C.Category2, C.Category3,
                    SUM(V.Visits) AS Visits
                FROM
                             RawData.GoogleAnalytics.Visits        AS V
                    INNER JOIN Routine.MarketingDashboardCampaignMap AS C ON V.LandingPage = C.LandingPage AND V.Campaign = C.Campaign AND V.Medium = C.Medium AND V.Referrer = C.Referrer AND V.Source = C.Source
                    LEFT JOIN  Staging.Volusion.MarketingRegions     AS MR ON V.Country = MR.CountryName
                WHERE
                    V.Date BETWEEN @StartDate AND @EndDate
                GROUP BY
                    V.Date,
                    CASE    WHEN V.Country IN ('United Kingdom', 'Guernsey', 'Ireland', 'Jersey') THEN 'UK'
                        WHEN V.Country IN ('United States', 'Canada', 'Puerto Rico', 'U.S. Virgin Islands') THEN 'US'
                        ELSE 'IN' END,
                    COALESCE(MR.Region, 'Unknown'), C.Category1, C.Category2, C.Category3
                ) AS V ON D.Date = V.Date AND Div.Division = V.Division AND Region.Region = V.Region AND C.Category1 = V.Category1 AND C.Category2 = V.Category2 AND C.Category3 = V.Category3
            -- Demos
            LEFT JOIN
                (
                SELECT
                    OD.SQLDate,
                    G.Division,
                    COALESCE(MR.Region,   'Unknown') AS Region,
                    COALESCE(C.Category1, 'Unknown') AS Category1,
                    COALESCE(C.Category2, 'Unknown') AS Category2,
                    COALESCE(C.Category3, 'Unknown') AS Category3,
                    SUM(D.Demos) AS Demos
                FROM
                             Demos            AS D
                    INNER JOIN Orders           AS O  ON D."Order" = O."Order"
                    INNER JOIN Dates            AS OD ON O.OrderDate = OD.DateSerial
                    INNER JOIN MarketingSources AS MS ON D.Source = MS.Source
                    LEFT JOIN  RegionsByCode    AS MR ON MS.CountryCode = MR.CountryCode
                    LEFT JOIN
                        (
                        SELECT
                            G.TransactionID,
                            MIN (
                                CASE WHEN G.Country IN ('United Kingdom', 'Guernsey', 'Ireland', 'Jersey') THEN 'UK'
                                    WHEN G.Country IN ('United States', 'Canada', 'Puerto Rico', 'U.S. Virgin Islands') THEN 'US'
                                    ELSE 'IN' END
                                ) AS Division
                        FROM
                            RawData.GoogleAnalytics.Geography AS G
                        WHERE
                                TransactionDate BETWEEN @StartDate AND @EndDate
                            AND NOT EXISTS (SELECT * FROM RawData.GoogleAnalytics.Geography AS G2 WHERE G.TransactionID = G2.TransactionID AND G2.EffectiveDate > G.EffectiveDate)
                        GROUP BY
                            G.TransactionID
                        ) AS G  ON O.VolusionOrderID = G.TransactionID
                    LEFT JOIN  RawData.GoogleAnalytics.Referrers     AS R  ON O.VolusionOrderID = R.TransactionID AND NOT EXISTS (SELECT * FROM RawData.GoogleAnalytics.Referrers AS R2 WHERE R.TransactionID = R2.TransactionID AND R2.EffectiveDate > R.EffectiveDate)
                    LEFT JOIN  Routine.MarketingDashboardCampaignMap AS C  ON MS.LandingPage = C.LandingPage AND MS.Campaign = C.Campaign AND MS.Medium = C.Medium AND COALESCE(R.ReferralPath, '(not set)') = C.Referrer AND MS.SourceName = C.Source
                WHERE
                        O.IsDeleted = 'No'
                    AND OD.SQLDate BETWEEN @StartDate AND @EndDate
                GROUP BY
                    OD.SQLDate,
                    G.Division,
                    COALESCE(MR.Region,   'Unknown'),
                    COALESCE(C.Category1, 'Unknown'),
                    COALESCE(C.Category2, 'Unknown'),
                    COALESCE(C.Category3, 'Unknown')
                ) AS Dem ON D.Date = Dem.SQLDate AND Div.Division = Dem.Division AND Region.Region = Dem.Region AND C.Category1 = Dem.Category1 AND C.Category2 = Dem.Category2 AND C.Category3 = Dem.Category3
            -- Stores
            LEFT JOIN
                (
                SELECT
                    OD.SQLDate,
                    CASE WHEN O.VolusionCountryCode = 'GB' THEN 'UK'
                        WHEN A.CountryShortName IN ('U.S.', 'Canada', 'Puerto Rico', 'U.S. Virgin Islands') THEN 'US'
                        ELSE 'IN' END AS Division,
                    COALESCE(MR.Region,     'Unknown') AS Region,
                    COALESCE(CpM.Category1, 'Unknown') AS Category1,
                    COALESCE(CpM.Category2, 'Unknown') AS Category2,
                    COALESCE(CpM.Category3, 'Unknown') AS Category3,
                    SUM(S.Stores) AS GrossStores,
                    SUM(CASE WHEN O.DatePaid <> -1 THEN 1 ELSE 0 END) AS PaidStores,
                    SUM(CASE WHEN O.DatePaid <> -1 AND CD.WeekEnding <> OD.WeekEnding THEN 1 ELSE 0 END) AS NetStores,
                    SUM(CASE WHEN O.DatePaid <> -1 THEN SH.ActiveStores ELSE 0 END) AS StoresActiveNow
                FROM
                             Stores           AS S
                    INNER JOIN Orders           AS O   ON S."Order" = O."Order"
                    INNER JOIN Dates            AS OD  ON O.OrderDate = OD.DateSerial
                    INNER JOIN Dates            AS CD  ON O.CancellationDate = CD.DateSerial
                    INNER JOIN Customers        AS C   ON O.CustomerNow = C.Customer
                    INNER JOIN MarketingSources AS MS  ON C.Source = MS.Source
                    INNER JOIN StoreHistory     AS SH  ON S.MostRecentHistory = SH.History
                    INNER JOIN Addresses        AS A   ON C.Address = A.Address
                    LEFT JOIN  RegionsByCode    AS MR  ON MS.CountryCode = MR.CountryCode
                    LEFT JOIN  Routine.MarketingDashboardCampaignMap AS CpM ON CpM.LandingPage = 'N/A' AND MS.Campaign = CpM.Campaign AND MS.Medium = CpM.Medium AND CpM.Referrer = 'N/A' AND MS.SourceName = CpM.Source
                WHERE
                        O.IsDeleted = 'No'
                    AND OD.SQLDate BETWEEN @StartDate AND @EndDate
                GROUP BY
                    OD.SQLDate,
                    CASE WHEN O.VolusionCountryCode = 'GB' THEN 'UK'
                        WHEN A.CountryShortName IN ('U.S.', 'Canada', 'Puerto Rico', 'U.S. Virgin Islands') THEN 'US'
                        ELSE 'IN' END,
                    COALESCE(MR.Region,     'Unknown'),
                    COALESCE(CpM.Category1, 'Unknown'),
                    COALESCE(CpM.Category2, 'Unknown'),
                    COALESCE(CpM.Category3, 'Unknown')
                ) AS S ON D.Date = S.SQLDate AND Div.Division = S.Division AND Region.Region = S.Region AND C.Category1 = S.Category1 AND C.Category2 = S.Category2 AND C.Category3 = S.Category3
            -- Google Analytics spend
            LEFT JOIN
                (
                SELECT
                    AC.Date, C.Category1, C.Category2, C.Category3, SUM(AC.AdCost) / SUM(AC.Visits) AS AvgAdCost
                FROM
                    RawData.GoogleAnalytics.AdCosts AS AC
                    INNER JOIN
                        (
                        SELECT Campaign, Medium, Source, MIN(Category1) AS Category1, MIN(Category2) AS Category2, MIN(Category3) AS Category3
                        FROM Routine.MarketingDashboardCampaignMap
                        WHERE Category1 <> 'Affiliate'
                        GROUP BY Campaign, Medium, Source
                        ) AS C ON AC.Campaign = C.Campaign AND AC.Medium = C.Medium AND AC.Source = C.Source
                WHERE
                    AC.Date BETWEEN @StartDate AND @EndDate
                GROUP BY
                    AC.Date, C.Category1, C.Category2, C.Category3
                HAVING
                    SUM(AC.AdCost) > 0.00 AND SUM(AC.Visits) > 0
                ) AS GAAC ON D.Date = GAAC.Date AND C.Category1 = GAAC.Category1 AND C.Category2 = GAAC.Category2 AND C.Category3 = GAAC.Category3
            -- adCenter spend
            LEFT JOIN
                (
                SELECT Date, SUM(Spend) / SUM(Clicks) AS AvgClickCost
                FROM RawData.AdCenter.Spend
                WHERE Date BETWEEN @StartDate AND @EndDate
                GROUP BY Date
                HAVING SUM(Spend) > 0.00 AND SUM(Clicks) > 0
                ) AS ACS ON D.Date = ACS.Date AND C.Category1 = 'PPC' AND C.Category2 = 'adCenter' AND C.Category3 = 'N/A'
        WHERE
            V.Visits > 0 OR Dem.Demos > 0 OR S.GrossStores > 0
GO


SELECT * FROM Routine.MarketingDashboardECommerceBase('2011-05-21', '2011-05-23')

Lütfen metin sorgu planlarını gösterebilir misiniz? Ve ilk sorguda, @StartDate + @EndDate
gbn

@gbn: Üzgünüz, plan çok uzun, yaklaşık 32 bin karakter. En yararlı olabilecek bazı altkümeler var mı? Ayrıca, bağımsız sorgu veya TVF için planı tercih eder misiniz?
Tüm Ticaretten Jon

Sorgunun TVF formunda yürütme planını çalıştırmak yararlı bir bilgi döndürmez, bu yüzden TVF olmayan sürümün sorgu planını aradığınızı varsayalım. Yoksa aslında bir TVF tarafından kullanılan yürütme planına ulaşmanın bir yolu var mı?
Tüm Ticaretten Jon

Bekleme görevi yok. Ben dm_exec_requests aşina değilim, ama çıkış TVF yürütme beş dakikalık işareti olarak ekledim.
Tüm Ticaretlerden Jon

@Martin: Evet; bağımsız sorgunun CPU süresi 7021 ( kısmi TVF sürümünün% 2'si) ve 154K mantıksal okuma (% 0,5) idi. Kısa bir süre önce TVF sürümünü çalıştırmak için ayrıldım ve 27 dakika sonra bitti. Yani kesinlikle çok daha fazla veriyi çalkalıyor ... ama daha iyi bir plan kullanmasını nasıl sağlayabilirim? İyi uygulama planını ayrıntılı olarak inceleyeceğim ve birkaç ipucunun yardımcı olup olmadığını göreceğim.
Tüm Ticaretlerden Jon

Yanıtlar:


3

Sorgudaki bir satıra sorun izole ettim. Sorgunun 160 satır uzunluğunda olduğunu ve bu satırı SELECT yan tümcesinden devre dışı bırakırsam, ilgili tabloları her iki şekilde de ekliyorum:

COALESCE(V.Visits, 0) * COALESCE(ACS.AvgClickCost, GAAC.AvgAdCost, 0.00)

... çalışma süresi 63 dakikadan beş saniyeye düşer (bir CTE'yi satırlamak, orijinal yedi saniyelik sorgudan biraz daha hızlı hale getirir). Ya dahil etmek ya ACS.AvgClickCostda GAAC.AvgAdCostçalışma zamanının patlamasına neden olur. Özellikle garip kılan şey, bu alanların sırasıyla on sıra ve üç olan iki alt sorgudan gelmesidir! Her biri bağımsız olarak çalıştığında sıfır saniye içinde çalışır ve satır sayıları çok kısa olduğunda, iç içe döngüleri bile birleştirmenin zamanının önemsiz olmasını beklerdim.

Tek başına bir sorgu olarak çok hızlı bir şekilde çalışırken, bu görünüşte zararsız hesaplamanın neden bir TVF'yi tamamen attığına dair herhangi bir tahmin var mı?


Sorguyu yayınladım, ancak gördüğünüz gibi, bazı görünümler ve bir TVF de dahil olmak üzere bir düzine masaya çiziliyor, bu yüzden yardımcı olmayacağından korkuyorum. Anlamadığım bölüm, bir TVF'ye bir sorgunun sarılmasının çalışma süresini 750 ile nasıl çarpabileceğidir. Yalnızca dahil edersem olur GAAC.AvgAdCost(bugün; dün ACS.AvgClickCostde bir problemdi), böylece alt sorgu yürütme planını atıyor gibi görünüyor .
Tüm Ticaretten Jon

1
Sanırım alt sorgular için birleştirme maddesine bakmalısınız. Tablolardan herhangi biri arasında çoktan çoğa ilişki kurarsanız, işlemek için 10 kat daha fazla kayıt alırsınız.

(İç içe görünümler sürü ve satır içi TVFs vardır) proje üzerinde bir noktada, kendimizi değiştirerek buldum COALESCE()ile ISNULL()yardım için sorgu iyileştirici taslak daha iyi planlar. Bence ISNULL()daha tahmin edilebilir bir çıktı türüne sahip olmak zorundaydı COALESCE(). Denemeye değer? Bunun belirsiz olduğunu biliyorum, ancak sınırlı deneyimimizde, sorgu optimize ediciyi daha iyi planlara doğru etkilemek bulanık bir sanat gibi görünüyor, bu yüzden umutsuzluktan bir sürü belirsiz çılgın fikir denemek ilerleme kaydetmenin tek yoludur.

2

Bu parametre koklama ile ilgisi bekliyoruz.

Bazı sorunlar burada hakkında konuşmak (ve parametre koklama için SO arayabilirsiniz.)

http://blogs.msdn.com/b/queryoptteam/archive/2006/03/31/565991.aspx


Satır içi TVF'lerle parametre kokusu almazsınız: bunlar yalnızca görünümler gibi genişleyen makrolardır.
gbn

@gbn: TVF'nin kendisinin bir makro gibi genişletildiği doğru olabilir, ancak (anladığım kadarıyla), genişlemenin bu genişlemeyi yürüten sorgu veya sproc, planlama ve potansiyel parametrelemeye tabidir. (SQL Server 2005'te bununla bir süre önce savaştık. SQL Server Management Studio'yu ARITHABORTReporting Services ve / veya jTDS'den farklı oturum ayarları ( belki?) Kullanarak bulana kadar mücadele özellikle zordu , bu yüzden bunlardan biri bazen "kötü" bir plan ama diğerleri (sorgulayıcı bir şekilde) "aynı sorguda" iyi olur.)

Bana koklamak gibi kokuyor ....
Hogan

Hmm, yapılacak çok şey var. Değeri ne olursa olsun, parametreleştirilmiş değerler için önemli bir fark yoktur: sorgu, tarih başına tek bir satır içeren bir Tarih tablosu ve tarih başına çok sayıda satır içeren birkaç tablo, ancak herhangi bir tarih için yaklaşık aynı sayı içerir. Aynı parametreleri (05/21 - 05/23) UDF'yi oluşturduktan (yeniden) hemen sonra bir test yürütmesinde kullanıyorum, bu yüzden eğer bu değerler için "astarlanmalıdır".
Tüm Ticaretten Jon

Bir not daha: parametrelerin değerlerini stackoverflow.com/questions/211355/… adresinde Jetson tarafından açıklandığı gibi yerel değişkenlere atamanın önemli bir etkisi yoktu.
Tüm Ticaretten Jon

1

Ne yazık ki SQL'in sorgu optimizasyon motoru iç fonksiyonları göremiyor.

Bu yüzden TF'de hangi ipuçlarının uygulanacağını anlamak için hızlı olandan yürütme planını kullanırdım. Durulayın ve TF'nin uygulama planı daha hızlı olana yaklaşana kadar tekrarlayın.

http://sqlblog.com/blogs/tibor_karaszi/archive/2008/08/29/execution-plan-re-use-sp-executesql-and-tsql-variables.aspx


2
SQL Server Sorgu Optimize Edici , ITVF'lerin içinde (satır içi tablo değerli işlevler) görebilir, ancak başkalarını göremez.

Not: Doğru şekilde tasarlandığında çapraz uygulanan satır içi tablo işlevleri, performansta büyük bir artışa neden olabilir. Örneğin, birleşiminiz gibi bir birleşimde ayrıştırılamayan bir ifade bir uygulama deyimine sarılabilir, bir küme olarak değerlendirilebilir ve daha sonra RBAR haline gelmeden bir sonraki sorguda birleştirilebilir. Biraz deneyin. Çapraz uygulama ustalaşmak zor, ama buna değer!
SheldonH

0

Bu değerler arasındaki farklar nelerdir lütfen?

arithabort              1
ansi_null_dflt_on       1
ansi_defaults           0
ansi_warnings           1
ansi_padding            1
ansi_nulls              1

Bunların (özellikle arithabort) sorgu performansını bu şekilde ciddi şekilde etkilediği gösterilmiştir.


Çünkü arithabortkendisi hakkında herhangi bir şey yerine bir plan önbellek anahtarı değil mi? SQL Server 2005'ten bu yana bu ayarınansi_warnings . (2000'de dizine eklenen görünümler yanlış ayarlanırsa kullanılmaz)
Martin Smith

@ Martin: Bu konuda doğrudan bir deneyimim yok ama son zamanlarda okumayı hatırladım. Ve bazı SO cevapları bulmak. OP yardımcı olabilir, olmayabilir ... Düzenle: sqlblog.com/blogs/kalen_delaney/archive/2008/06/19/… sigh
gbn

SO ile ilgili oldukça açık iddiaları okudum. Kendim için çoğaltmamı sağlayacak hiçbir şey görmedim ya da arithabortortamın neden performans üzerinde bu kadar dramatik bir etkiye sahip olması gerektiğine dair mantıklı bir açıklama görmedim, bu yüzden şu anda biraz şüpheliyim.
Martin Smith

ARITHABORT, ANSI_WARNINGS, ANSI_PADDING ve ANSI_NULL 1, geri kalanı NULL.
Tüm Ticaretlerden Jon

FYI, tamamen SSMS'de çalışıyorum, bu yüzden VS veya diğer istemcilerdeki farklı ayarlar sorun değil.
Tüm Ticaretlerden Jon
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.