Tarih karşılaştırmaları ile düşük performans gösteren alt sorgu


15

Eşleşen bir alana sahip önceki tüm kayıtların toplam sayısını bulmak için bir alt sorgu kullanırken, performans 50 bin kadar az kayıt içeren bir tabloda korkunçtur. Alt sorgu olmadan, sorgu birkaç milisaniye içinde yürütülür. Alt sorgu ile yürütme süresi bir dakikadır.

Bu sorgu için sonuç:

  • Yalnızca belirli bir tarih aralığındaki kayıtları dahil et.
  • Tarih aralığından bağımsız olarak, geçerli kayıt dahil değil önceki tüm kayıtların sayısını ekleyin.

Temel Tablo Şeması

Activity
======================
Id int Identifier
Address varchar(25)
ActionDate datetime2
Process varchar(50)
-- 7 other columns

Örnek Veriler

Id  Address     ActionDate (Time part excluded for simplicity)
===========================
99  000         2017-05-30
98  111         2017-05-30
97  000         2017-05-29
96  000         2017-05-28
95  111         2017-05-19
94  222         2017-05-30

Beklenen sonuçlar

Tarih aralığı 2017-05-29için2017-05-30

Id  Address     ActionDate    PriorCount
=========================================
99  000         2017-05-30    2  (3 total, 2 prior to ActionDate)
98  111         2017-05-30    1  (2 total, 1 prior to ActionDate)
94  222         2017-05-30    0  (1 total, 0 prior to ActionDate)
97  000         2017-05-29    1  (3 total, 1 prior to ActionDate)

96 ve 95 kayıtları sonuçtan hariç tutulur, ancak PriorCountalt sorguya dahil edilir

Geçerli Sorgu

select 
    *.a
    , ( select count(*) 
        from Activity
        where 
            Activity.Address = a.Address
            and Activity.ActionDate < a.ActionDate
    ) as PriorCount
from Activity a
where a.ActionDate between '2017-05-29' and '2017-05-30'
order by a.ActionDate desc

Mevcut Dizin

CREATE NONCLUSTERED INDEX [IDX_my_nme] ON [dbo].[Activity]
(
    [ActionDate] ASC
)
INCLUDE ([Address]) WITH (
    PAD_INDEX = OFF, 
    STATISTICS_NORECOMPUTE = OFF, 
    SORT_IN_TEMPDB = OFF, 
    DROP_EXISTING = OFF, 
    ONLINE = OFF, 
    ALLOW_ROW_LOCKS = ON, 
    ALLOW_PAGE_LOCKS = ON
)

Soru

  • Bu sorgunun performansını artırmak için hangi stratejiler kullanılabilir?

Edit 1
DB üzerinde ne değiştirebilirim sorusuna cevap olarak: Ben dizinleri, sadece tablo yapısını değiştirebilir.

Edit 2
Şimdi Addresssütuna temel bir dizin ekledim , ancak bu pek gelişmedi. Şu anda bir geçici tablo oluşturma ve değerleri olmadan ekleme PriorCountve daha sonra her satır belirli sayıları ile güncelleme ile çok daha iyi performans buluyorum .

Edit 3
Endeks Biriktirme Joe Obbish (kabul edilen cevap) sorun bulundu. Yeni bir kez eklediğimde, nonclustered index [xyz] on [Activity] (Address) include (ActionDate)geçici tablo kullanmadan sorgu süreleri bir dakikadan bir saniyeden bir saniyeye indirildi (bakınız düzenleme 2).

Yanıtlar:


17

Sahip olduğunuz dizin tanımı ile IDX_my_nmeSQL Server ActionDatesütunu kullanarak arayabilir, ancak sütunu kullanamaz Address. Dizin, alt sorguyu kapsamak için gereken tüm sütunları içerir, ancak bu alt sorgu için büyük olasılıkla çok seçici değildir. Tablodaki verilerin neredeyse hepsinin ActionDatedeğerinden daha eski olduğunu varsayalım '2017-05-30'. Bir arama ActionDate < '2017-05-30'işlemi, dizinden neredeyse tüm satırları döndürür; bu satır, dizinden getirildikten sonra tekrar filtrelenir. Sorgunuz 200 satır döndürürse, büyük olasılıkla neredeyse 200 tam dizin taraması IDX_my_nmeyaparsınız, yani dizinden yaklaşık 50000 * 200 = 10 milyon satır okuyacaksınız.

AddressSorgu hakkında bize tam istatistiksel bilgi vermemiş olsanız da, araştırmanın alt sorgunuz için çok daha seçici olması muhtemeldir, bu yüzden benim açımdan bir varsayım. Ancak, yalnızca bir dizin oluşturduğunuzu Addressve tablonuzun 10 bin benzersiz değerine sahip olduğunu varsayalım Address. Yeni dizinde, SQL Server'ın alt sorgunun her yürütülmesi için dizinden yalnızca 5 satır araması gerekir, böylece dizinden yaklaşık 200 * 5 = 1000 satır okuyacaksınız.

Bazı küçük sözdizimi farklılıkları olabilir SQL Server 2016 karşı test ediyorum. Aşağıda, veri dağıtımı için yukarıdakilere benzer varsayımlar yaptığım bazı örnek veriler verilmiştir:

CREATE TABLE #Activity (
    Id int NOT NULL,
    [Address] varchar(25) NULL,
    ActionDate datetime2 NULL,
    FILLER varchar(100),
    PRIMARY KEY (Id)
);

INSERT INTO #Activity WITH (TABLOCK)
SELECT TOP (50000) -- 50k total rows
x.RN
, x.RN % 10000 -- 10k unique addresses
, DATEADD(DAY, x.RN / 100, '20160201') -- 100 rows per day
, REPLICATE('Z', 100)
FROM
(
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) x;

CREATE NONCLUSTERED INDEX [IDX_my_nme] ON #Activity
([ActionDate] ASC) INCLUDE ([Address]);

Endeksinizi soruda açıklandığı gibi oluşturdum. Sorudaki ile aynı verileri döndüren bu sorguyu test ediyorum:

select 
    a.*
    , ( select count(*) 
        from #Activity Activity
        where 
            Activity.[Address] = a.[Address]
            and Activity.ActionDate < a.ActionDate
    ) as PriorCount
from #Activity a
where a.ActionDate between '2017-05-29' and '2017-05-30'
order by a.ActionDate desc;

Bir dizin biriktirme alıyorum. Temel düzeyde bunun anlamı, sorgu iyileştiricinin anında geçici bir dizin oluşturmasıdır, çünkü tabloya karşı varolan dizinlerden hiçbiri uygun değildir.

dizin makarası

Sorgu benim için hala çabuk bitiyor. Belki de sisteminizde dizin biriktirme optimizasyonunu almıyorsunuz veya tablo tanımı veya sorgu hakkında farklı bir şey var. Eğitim amaçlı olarak OPTION (QUERYRULEOFF BuildSpool), dizin biriktirmesini devre dışı bırakmak için belgesiz bir özellik kullanabilirim. Plan şöyle görünüyor:

kötü endeks arayışı

Basit bir indeks arayışı ile aldanmayın. SQL Server dizinden yaklaşık 10 milyon satır okur:

Dizinden 10 milyon satır

Sorguyu bir kereden fazla çalıştıracaksam, muhtemelen sorgu optimizer'ının her çalıştığında bir dizin oluşturması mantıklı değildir. Bu sorgu için daha seçici bir ön dizin oluşturabilir:

CREATE NONCLUSTERED INDEX [IDX_my_nme_2] ON #Activity
([Address] ASC) INCLUDE (ActionDate);

Plan öncekine benzer:

dizin araması

Ancak, yeni dizin ile SQL Server dizin sadece 1000 satır okur. Satırların 800'ü sayılmak üzere döndürülür. Dizin daha seçici olarak tanımlanabilir, ancak veri dağıtımınıza bağlı olarak bu yeterli olabilir.

iyi arayış

Tabloda herhangi bir ek dizin tanımlayamıyorsanız pencere işlevlerini kullanmayı düşünürüm. Aşağıdaki gibi çalışıyor:

SELECT t.*
FROM
(
    select 
        a.*
        , -1 + ROW_NUMBER() OVER (PARTITION BY [Address] ORDER BY ActionDate) PriorCount
    from #Activity a
) t
where t.ActionDate between '2017-05-29' and '2017-05-30'
order by t.ActionDate desc;

Bu sorgu, verilerin tek bir taramasını yapar, ancak pahalı bir sıralama yapar ve ROW_NUMBER()tablodaki her satır için işlevi hesaplar , bu nedenle burada bazı ekstra işler varmış gibi hisseder:

kötü sıralama

Ancak, bu kod modelini gerçekten seviyorsanız, daha verimli hale getirmek için bir dizin tanımlayabilirsiniz:

CREATE NONCLUSTERED INDEX [IDX_my_nme] ON #Activity
([Address], [ActionDate]) INCLUDE (FILLER);

Bu, sıralamayı çok daha ucuza gelecek şekilde sonlandırır:

iyi tür

Bunların hiçbiri yardımcı olmazsa, soruya tercihen gerçek yürütme planları da dahil olmak üzere daha fazla bilgi eklemeniz gerekir.


1
Bulduğunuz Dizin Biriktirme sorunu oldu. Bir kez yeni bir ekledi nonclustered index [xyz] on [Activity] (Address) include (ActionDate), sorgu süreleri bir dakika yukarı bir saniyeden daha az düştü. Yapabilirsem +10. Teşekkürler!
Metro Şirin,
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.