İki sütun eklenirken sorgu yanıt vermiyor


9

Seçime iki sütun eklediğimde sorgu yanıt vermiyor. Sütun türü nvarchar(2000). Biraz alışılmadık.

  • SQL Server sürümü 2014'tür.
  • Yalnızca bir birincil dizin vardır.
  • Tüm kayıtlar sadece 1000 satırdır.

Daha önce yürütme planı ( XML showplan ):

resim açıklamasını buraya girin

Sonra yürütme planı ( XML showplan ):

resim açıklamasını buraya girin

İşte sorgu:

select top(100)
  Batch_Tasks_Queue.id,
  btq.id,
  Batch_Tasks_Queue.[Parameters], -- this field
  btq.[Parameters]  -- and this field
from
        Batch_Tasks_Queue with(nolock)
    inner join  Batch_Tasks_Queue btq with(nolock)  on  Batch_Tasks_Queue.Start_Time < btq.Start_Time
                            and btq.Start_Time < Batch_Tasks_Queue.Finish_Time
                            and Batch_Tasks_Queue.id <> btq.id                            
                            and btq.Start_Time is not null
                            and btq.State in (3, 4)                          
where
    Batch_Tasks_Queue.Start_Time is not null      
    and Batch_Tasks_Queue.State in (3, 4)
    and Batch_Tasks_Queue.Operation_Type = btq.Operation_Type
    and Batch_Tasks_Queue.Operation_Type not in (23, 24, 25, 26, 27, 28, 30)

order by
    Batch_Tasks_Queue.Start_Time desc

Tüm sonuç sayısı 17 satırdır. Kirli veriler (nolock ipucu) önemli değildir.

İşte tablo yapısı:

CREATE TABLE [dbo].[Batch_Tasks_Queue](
    [Id] [int] NOT NULL,
    [OBJ_VERSION] [numeric](8, 0) NOT NULL,
    [Operation_Type] [numeric](2, 0) NULL,
    [Request_Time] [datetime] NOT NULL,
    [Description] [varchar](1000) NULL,
    [State] [numeric](1, 0) NOT NULL,
    [Start_Time] [datetime] NULL,
    [Finish_Time] [datetime] NULL,
    [Parameters] [nvarchar](2000) NULL,
    [Response] [nvarchar](max) NULL,
    [Billing_UserId] [int] NOT NULL,
    [Planned_Start_Time] [datetime] NULL,
    [Input_FileId] [uniqueidentifier] NULL,
    [Output_FileId] [uniqueidentifier] NULL,
    [PRIORITY] [numeric](2, 0) NULL,
    [EXECUTE_SEQ] [numeric](2, 0) NULL,
    [View_Access] [numeric](1, 0) NULL,
    [Seeing] [numeric](1, 0) NULL,
 CONSTRAINT [PKBachTskQ] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [Batch_Tasks_QueueData]
) ON [Batch_Tasks_QueueData] TEXTIMAGE_ON [Batch_Tasks_QueueData]
GO    
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue]  WITH NOCHECK ADD  CONSTRAINT [FK0_BtchTskQ_BlngUsr] FOREIGN KEY([Billing_UserId])
REFERENCES [dbo].[BILLING_USER] ([ID])
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue] CHECK CONSTRAINT [FK0_BtchTskQ_BlngUsr]
GO

Bu soru üzerine tartışma bu sohbet odasına taşındı .
Paul White 9

Yanıtlar:


15

özet

Ana sorunlar:

  • Optimize edicinin plan seçimi, değerlerin düzgün bir dağılımını varsayar.
  • Uygun indeks eksikliği:
    • Tabloyu taramak tek seçenektir.
    • Bir birleşim naif iç içe döngüler bir yerine, katılmak endeksi iç içe döngüler katılmak. Saf bir birleşmede, birleşim tahminleri birleşimin iç tarafına itilmek yerine birleşimde değerlendirilir.

ayrıntılar

İki plan temel olarak oldukça benzer, ancak performans çok farklı olabilir:

Ek sütunlarla planlama

Önce makul bir süre içinde tamamlanmayan fazladan sütunları olanı almak:

Yavaş plan

İlginç özellikler:

  1. 0 düğümündeki Üst 100'e döndürülen satırları sınırlar. Ayrıca, optimize edici için bir satır hedefi belirler, bu nedenle planın altındaki her şey ilk 100 satırı hızlı bir şekilde döndürmek için seçilir.
  2. Düğüm 4'te tara, Start_Timeboş olmayan State, 3 veya 4 Operation_Typeolan ve listelenen değerlerden biri olan tablodaki satırları bulur . Tablo bir kez tamamen taranır, her satır belirtilen tahminlere göre test edilir. Yalnızca tüm testleri geçen satırlar Sıralama'ya akar. Optimize edici, 38.283 satırın hak kazanacağını tahmin ediyor.
  3. Düğüm 3'teki sırala, düğüm 4'teki tara'daki tüm satırları tüketir ve sıralarına göre sıralar Start_Time DESC. Bu, sorgu tarafından istenen son sunum sırasıdır.
  4. Optimize edici, tüm planın 100 satır döndürmesi için 93 satırın (aslında 93.2791) Sırala'dan okunması gerektiğini tahmin eder (birleştirmenin beklenen etkisini hesaba katar).
  5. Düğüm 2'de birleştirilen İç İçe Döngülerin iç girdisini (alt dal) 94 kez (aslında 94.2791) yürütmesi beklenir. Ek satır, teknik nedenlerden dolayı düğüm 1'deki durdurma paralellik değişimiyle gereklidir.
  6. Düğüm 5'teki Tara, her yinelemede tabloyu tam olarak tarar. Start_TimeBoş olmayan ve State3 veya 4 olan satırları bulur . Bu, her yinelemede 400.875 satır ürettiği tahmin edilmektedir. 94.2791 yinelemenin üzerinde toplam satır sayısı neredeyse 38 milyon.
  7. Düğüm 2'deki İç İçe Döngüler birleşimi de birleşim tahminlerini uygular. O denetler Operation_Typeolduğunu, maçların Start_Timedüğümden 4 den az olduğu Start_Timeyani düğüm 5'ten Start_Timedüğümden 5 den az olan Finish_Timedüğüm 4'ten ve iki o Iddeğerleri eşleşmiyor.
  8. Düğüm 1'deki Akışları Topla (paralellik değişimini durdur), 100 sıra üretilinceye kadar her bir iplikten sıralı akışları birleştirir. Birden fazla akışta birleştirmenin düzenini koruyan doğası, 5. adımda bahsedilen fazladan satırı gerektiren şeydir.

Büyük verimsizlik açıkça yukarıdaki 6. ve 7. adımlardadır. Her yineleme için 5 numaralı düğümde tabloyu tam olarak taramak, optimize edicinin tahmin ettiği gibi yalnızca 94 kez gerçekleşirse bile biraz makul olabilir. Düğüm 2'deki sıra başına ~ 38 milyon karşılaştırma seti de büyük bir maliyettir.

En önemlisi, değerlerin dağılımına bağlı olduğu için 93/94 sıralı satır hedef tahmininin de yanlış olması muhtemeldir. Optimize edici, daha ayrıntılı bilgi olmadan tekdüze bir dağıtım olduğunu varsayar. Basit bir deyişle, tablodaki satırların% 1'inin nitelendirilmesi bekleniyorsa, optimize edici 1 eşleşen satır bulmanın nedenlerini belirtirse, 100 satırı okuması gerekir.

Bu sorguyu tamamlanmaya kadar çalıştırdıysanız (ki bu çok uzun zaman alabilir), büyük olasılıkla sonunda 100 satır üretmek için 93/94 satırdan daha fazlasının Sıradan okunması gerektiğini görürsünüz. En kötü durumda, 100. sıra Sıradaki son satır kullanılarak bulunacaktır. Optimize edicinin düğüm 4'teki tahmininin doğru olduğu varsayılırsa, bu, Taramayı düğüm 15'te 38.284 kez, toplamda 15 milyar sıra gibi çalıştırmak demektir . Tarama tahminleri de kapalıysa daha fazla olabilir.

Bu yürütme planı ayrıca eksik bir dizin uyarısı içerir:

/*
The Query Processor estimates that implementing the following index
could improve the query cost by 72.7096%.

WARNING: This is only an estimate, and the Query Processor is making
this recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide
impact, including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/

CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[Batch_Tasks_Queue] ([Operation_Type],[State],[Start_Time])
INCLUDE ([Id],[Parameters])

Optimize edici, tabloya bir dizin eklemenin performansı artıracağı konusunda sizi uyarıyor.

Fazladan sütunlar olmadan planlama

Daha az yavaş plan

Bu esasen öncekiyle aynı plan olup, düğüm 6'ya İndeks Biriktirme ve düğüm 5'e Filtre ekleme ile önemli farklar şunlardır:

  1. Düğüm 6'daki Dizin Biriktirme bir Eager Biriktir. Bu hevesle altındaki taramasının sonuçlarını tüketir ve üzerine kademeli geçici endeksi oluşturur Operation_Typeve Start_Timebirlikte Idolmayan bir anahtar sütun olarak.
  2. Düğüm 2'deki İç İçe Döngüler Birleştirme artık bir dizin birleşimidir. Resim birleştirme sayıda dayanak buradan yerine başına yineleme akım değerleri değerlendirilir Operation_Type, Start_Time, Finish_Timeve Iddüğüm 4 de tarama dış referans olarak iç yan dal geçirilir.
  3. Düğüm 7'de tarama yalnızca bir kez gerçekleştirilir.
  4. Düğüm 6 Endeksi biriktirme geçici dizinden satır arar Operation_Typemevcut dış referans değeri ile eşleşen ve Start_Timetanımlanan aralığındadır Start_Timeve Finish_Timedış referanslar.
  5. Düğüm 5'teki Filtre Id, Dizin Biriktiricisi'ndeki değerleri, geçerli dış referans değerine karşı eşitsizlik açısından test eder Id.

Önemli geliştirmeler:

  • İç taraf taraması sadece bir kez yapılır
  • Dahil edilen sütun olarak ( Operation_Type, Start_Time) üzerindeki geçici bir dizin, Idbir dizin iç içe döngülerin birleşmesine izin verir. Dizin, her seferinde tüm tabloyu taramak yerine her bir yinelemede eşleşen satırları aramak için kullanılır.

Daha önce olduğu gibi, optimize edici eksik bir dizin hakkında bir uyarı içerir:

/*
The Query Processor estimates that implementing the following index
could improve the query cost by 24.1475%.

WARNING: This is only an estimate, and the Query Processor is making
this recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide
impact, including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/

CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[Batch_Tasks_Queue] ([State],[Start_Time])
INCLUDE ([Id],[Operation_Type])
GO

Sonuç

Optimize edici sizin için geçici bir dizin oluşturmayı seçtiğinden fazladan sütun içermeyen plan daha hızlıdır.

Fazladan sütunlar içeren plan, geçici dizinin oluşturulmasını daha pahalı hale getirir. [Parameters] Sütun nvarchar(2000)indeksi her satır için 4000 bayt kadar ekleyin hangi. Ek maliyet, optimize ediciyi her bir yürütme için geçici dizin oluşturmanın kendisi için ödeme yapmayacağına ikna etmek için yeterlidir.

Doktor her iki durumda da kalıcı bir endeksin daha iyi bir çözüm olacağı konusunda uyarıyor. Endeksin ideal bileşimi daha geniş iş yükünüze bağlıdır. Bu özel sorgu için önerilen dizinler makul bir başlangıç ​​noktasıdır, ancak ilgili avantajları ve maliyetleri anlamanız gerekir.

Öneri

Bu sorgu için çok çeşitli olası indeksler faydalı olacaktır. Önemli paket, bir çeşit kümelenmemiş endeksin gerekli olmasıdır. Sağlanan bilgilerden, bence makul bir dizin:

CREATE NONCLUSTERED INDEX i1
ON dbo.Batch_Tasks_Queue (Start_Time DESC)
INCLUDE (Operation_Type, [State], Finish_Time);

Ben de sorguyu biraz daha iyi organize etmek ve [Parameters]en iyi 100 satır ( Idanahtar olarak kullanarak ) bulunana kadar kümelenmiş dizinde geniş sütunları ararken gecikme cazip olurdu :

SELECT TOP (100)
    BTQ1.id,
    BTQ2.id,
    BTQ3.[Parameters],
    BTQ4.[Parameters]
FROM dbo.Batch_Tasks_Queue AS BTQ1
JOIN dbo.Batch_Tasks_Queue AS BTQ2 WITH (FORCESEEK)
    ON BTQ2.Operation_Type = BTQ1.Operation_Type
    AND BTQ2.Start_Time > BTQ1.Start_Time
    AND BTQ2.Start_Time < BTQ1.Finish_Time
    AND BTQ2.id != BTQ1.id
    -- Look up the [Parameters] values
JOIN dbo.Batch_Tasks_Queue AS BTQ3
    ON BTQ3.Id = BTQ1.Id
JOIN dbo.Batch_Tasks_Queue AS BTQ4
    ON BTQ4.Id = BTQ2.Id
WHERE
    BTQ1.[State] IN (3, 4)
    AND BTQ2.[State] IN (3, 4)
    AND BTQ1.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
    AND BTQ2.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
    -- These predicates are not strictly needed
    AND BTQ1.Start_Time IS NOT NULL
    AND BTQ2.Start_Time IS NOT NULL
ORDER BY
    BTQ1.Start_Time DESC;

Nerede [Parameters]sütunlar gerekli değildir, sorgu basitleştiirlebilir:

SELECT TOP (100)
    BTQ1.id,
    BTQ2.id
FROM dbo.Batch_Tasks_Queue AS BTQ1
JOIN dbo.Batch_Tasks_Queue AS BTQ2 WITH (FORCESEEK)
    ON BTQ2.Operation_Type = BTQ1.Operation_Type
    AND BTQ2.Start_Time > BTQ1.Start_Time
    AND BTQ2.Start_Time < BTQ1.Finish_Time
    AND BTQ2.id != BTQ1.id
WHERE
    BTQ1.[State] IN (3, 4)
    AND BTQ2.[State] IN (3, 4)
    AND BTQ1.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
    AND BTQ2.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
    AND BTQ1.Start_Time IS NOT NULL
    AND BTQ2.Start_Time IS NOT NULL
ORDER BY
    BTQ1.Start_Time DESC;

FORCESEEKİpucu iyileştirici bir endeksli iç içe döngüler planı seçer sağlamak yardımına yoktur (kuyu bu tür çalışmalara değil eğilimindedir aksi bir karma ya da (çok-çok) birleştirme katılmak seçmek için optimize edici için maliyet esaslı günaha vardır Her ikisi de büyük artıklarla sonuçlanır; karma durumunda kova başına birçok öğe ve birleştirme için birçok geri sarma).

Alternatif

Sorgu (belirli değerleri dahil) özellikle okuma performansı için kritik olsaydı, bunun yerine iki filtrelenmiş dizin düşünürdüm:

CREATE NONCLUSTERED INDEX i1
ON dbo.Batch_Tasks_Queue (Start_Time DESC)
INCLUDE (Operation_Type, [State], Finish_Time)
WHERE 
    Start_Time IS NOT NULL
    AND [State] IN (3, 4)
    AND Operation_Type <> 23
    AND Operation_Type <> 24
    AND Operation_Type <> 25
    AND Operation_Type <> 26
    AND Operation_Type <> 27
    AND Operation_Type <> 28
    AND Operation_Type <> 30;

CREATE NONCLUSTERED INDEX i2
ON dbo.Batch_Tasks_Queue (Operation_Type, [State], Start_Time)
WHERE 
    Start_Time IS NOT NULL
    AND [State] IN (3, 4)
    AND Operation_Type <> 23
    AND Operation_Type <> 24
    AND Operation_Type <> 25
    AND Operation_Type <> 26
    AND Operation_Type <> 27
    AND Operation_Type <> 28
    AND Operation_Type <> 30;

[Parameters]Sütuna ihtiyaç duymayan sorgu için, filtrelenmiş dizinleri kullanan tahmini plan:

Basit filtrelenmiş dizin planı

Dizin taraması, ek tahminleri değerlendirmeden tüm uygun satırları otomatik olarak döndürür. İç içe geçmiş döngülerin birleştiği her yineleme için, dizin araması iki arama işlemi gerçekleştirir:

  1. Üzerinde bir arama öneki eşleşmesi Operation_Typeve State= 3, daha sonra Start_Timedeğerlerin aralığını , Ideşitsizlik üzerine artık yüklemeyi aramak .
  2. Üzerinde bir arama öneki eşleşmesi Operation_Typeve State= 4, daha sonra Start_Timedeğerlerin aralığını , Ideşitsizlik üzerine artık yüklemeyi aramak .

[Parameters]Sütunun gerekli olduğu yerlerde, sorgu planı her tablo için en fazla 100 tekil arama ekler:

Ek sütunlarla filtrelenmiş dizin planı

Son bir not olarak, numericuygulanabilir olduğu yerde yerleşik standart tamsayı türlerini kullanmayı düşünmelisiniz .


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.