Performansı yavaşlatan Anahtar Arama (Kümelenmiş) operatörünü ortadan kaldırın


16

Yürütme planımda Anahtar Arama (Kümelenmiş) operatörünü nasıl kaldırabilirim?

Tablo tblQuoteszaten kümelenmiş bir dizin (on QuoteID) ve 27 kümelenmemiş dizin vardır, bu yüzden daha fazla oluşturmamaya çalışıyorum.

Kümelenmiş dizin sütununu QuoteIDyardımcı olacağını umarak sorguma koydum - ama ne yazık ki hala aynı.

Burada icra planı .

Veya görüntüleyin:

resim açıklamasını buraya girin

Anahtar Arama işlecinin söylediği şey:

resim açıklamasını buraya girin

Sorgu:

declare
        @EffDateFrom datetime ='2017-02-01',
        @EffDateTo   datetime ='2017-08-28'

SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

IF OBJECT_ID('tempdb..#Data') IS NOT NULL
    DROP TABLE #Data 
CREATE TABLE #Data
(
    QuoteID int NOT NULL,   --clustered index

    [EffectiveDate] [datetime] NULL, --not indexed
    [Submitted] [int] NULL,
    [Quoted] [int] NULL,
    [Bound] [int] NULL,
    [Exonerated] [int] NULL,
    [ProducerLocationId] [int] NULL,
    [ProducerName] [varchar](300) NULL,
    [BusinessType] [varchar](50) NULL,
    [DisplayStatus] [varchar](50) NULL,
    [Agent] [varchar] (50) NULL,
    [ProducerContactGuid] uniqueidentifier NULL
)
INSERT INTO #Data
    SELECT 
        tblQuotes.QuoteID,

          tblQuotes.EffectiveDate,
          CASE WHEN lstQuoteStatus.QuoteStatusID >= 1   THEN 1 ELSE 0 END AS Submitted,
          CASE WHEN lstQuoteStatus.QuoteStatusID = 2 or lstQuoteStatus.QuoteStatusID = 3 or lstQuoteStatus.QuoteStatusID = 202 THEN 1 ELSE 0 END AS Quoted,
          CASE WHEN lstQuoteStatus.Bound = 1 THEN 1 ELSE 0 END AS Bound,
          CASE WHEN lstQuoteStatus.QuoteStatusID = 3 THEN 1 ELSE 0 END AS Exonareted,
          tblQuotes.ProducerLocationID,
          P.Name + ' / '+ P.City as [ProducerName], 
        CASE WHEN tblQuotes.PolicyTypeID = 1 THEN 'New Business' 
             WHEN tblQuotes.PolicyTypeID = 3 THEN 'Rewrite'
             END AS BusinessType,
        tblQuotes.DisplayStatus,
        tblProducerContacts.FName +' '+ tblProducerContacts.LName as Agent,
        tblProducerContacts.ProducerContactGUID
FROM    tblQuotes 
            INNER JOIN lstQuoteStatus 
                on tblQuotes.QuoteStatusID=lstQuoteStatus.QuoteStatusID
            INNER JOIN tblProducerLocations P 
                On P.ProducerLocationID=tblQuotes.ProducerLocationID
            INNER JOIN tblProducerContacts 
                ON dbo.tblQuotes.ProducerContactGuid = tblProducerContacts.ProducerContactGUID

WHERE   DATEDIFF(D,@EffDateFrom,tblQuotes.EffectiveDate)>=0 AND DATEDIFF(D, @EffDateTo, tblQuotes.EffectiveDate) <=0
        AND dbo.tblQuotes.LineGUID = '6E00868B-FFC3-4CA0-876F-CC258F1ED22D'--Surety
        AND tblQuotes.OriginalQuoteGUID is null

select * from #Data

Yürütme planı:

resim açıklamasını buraya girin


Tahmini ve Gerçek satırlar dikkate değer bir fark gösterir. Belki de SQL iyi bir tahmin yapmak için verilere sahip olmadığı için kötü bir plan seçer. İstatistiklerinizi ne sıklıkla güncellersiniz?
RDFozz

Yanıtlar:


23

Sorgu işlemcisinin, sorgunun sonuçları döndürmesi için gereken satırları bulmak için kullanılan dizinde depolanmayan sütunlardan değer alması gerektiğinde, çeşitli tatların anahtar aramaları gerçekleşir.

Örneğin, tek bir dizine sahip bir tablo oluşturduğumuz şu kodu ele alalım:

USE tempdb;

IF OBJECT_ID(N'dbo.Table1', N'U') IS NOT NULL
DROP TABLE dbo.Table1
GO

CREATE TABLE dbo.Table1
(
    Table1ID int NOT NULL IDENTITY(1,1)
    , Table1Data nvarchar(30) NOT NULL
);

CREATE INDEX IX_Table1
ON dbo.Table1 (Table1ID);
GO

Tabloya 1.000.000 satır ekleyeceğiz, böylece üzerinde çalışmak için bazı verilerimiz var:

INSERT INTO dbo.Table1 (Table1Data)
SELECT TOP(1000000) LEFT(c.name, 30)
FROM sys.columns c
    CROSS JOIN sys.columns c1
    CROSS JOIN sys.columns c2;
GO

Şimdi, verileri "gerçek" yürütme planını görüntüleme seçeneğiyle sorgulayacağız:

SELECT *
FROM dbo.Table1
WHERE Table1ID = 500000;

Sorgu planı şunları gösterir:

resim açıklamasını buraya girin

Sorgu, IX_Table1dizini bulmak için dizine bakar Table1ID = 5000000çünkü bu dizine bakmak, bu değeri arayan tüm tabloyu taramaktan çok daha hızlıdır. Ancak, sorgu sonuçlarını karşılamak için sorgu işlemcisinin tablodaki diğer sütunların değerini de bulması gerekir; burada "RID Araması" devreye girer. Sütundan Table1IDdeğerleri elde ederek 500000 değerini içeren satırla ilişkili satır kimliği (RID Arama'daki RID) tablosuna bakar Table1Data. Fareyi plandaki "RID Arama" düğümü üzerine getirirseniz, şunu görürsünüz:

resim açıklamasını buraya girin

"Çıktı Listesi", RID Araması tarafından döndürülen sütunları içerir.

Kümelenmiş bir dizine ve kümelenmemiş bir dizine sahip bir tablo ilginç bir örnek oluşturur. Aşağıdaki tabloda üç sütun vardır; DatKümelenmemiş bir dizin IX_Tableve üçüncü bir sütun tarafından dizinlenen küme anahtarı olan kimlik Oth,.

USE tempdb;

IF OBJECT_ID(N'dbo.Table1', N'U') IS NOT NULL
DROP TABLE dbo.Table1
GO

CREATE TABLE dbo.Table1
(
    ID int NOT NULL IDENTITY(1,1) 
        PRIMARY KEY CLUSTERED
    , Dat nvarchar(30) NOT NULL
    , Oth nvarchar(3) NOT NULL
);

CREATE INDEX IX_Table1
ON dbo.Table1 (Dat);
GO

INSERT INTO dbo.Table1 (Dat, Oth)
SELECT TOP(1000000) CRYPT_GEN_RANDOM(30), CRYPT_GEN_RANDOM(3)
FROM sys.columns c
    CROSS JOIN sys.columns c1
    CROSS JOIN sys.columns c2;
GO

Bu örnek sorguyu alın:

SELECT *
FROM dbo.Table1
WHERE Dat = 'Test';

SQL Server'dan, Datsütunun kelimeyi içerdiği tablodan her sütunu döndürmesini istiyoruz Test. Burada birkaç seçeneğimiz var; Masanın (yani kümelenmiş indeks) bakabilirsiniz - ama bu tablo tarafından sipariş edildiğinden şeyin tamamını tarayarak yol açmak olur IDbize satır (lar) içeren hakkında hiçbir şey anlatır sütununda, Testiçinde Datkolona. Diğer seçenek (ve SQL Server tarafından seçilen seçenek) IX_Table1satırı bulmak için kümelenmemiş dizini aramaktan oluşur Dat = 'Test', ancak Othsütuna da ihtiyacımız olduğundan SQL Server'ın "Anahtar kullanarak kümelenmiş dizine bir arama yapması gerekir. Arama "işlemi. Bunun planı:

resim açıklamasını buraya girin

Biz o kadar olmayan kümelenmiş bir dizin değiştirirseniz içerirOth sütunu:

DROP INDEX IX_Table1
ON dbo.Table1;
GO

CREATE INDEX IX_Table1
ON dbo.Table1 (Dat)
INCLUDE (Oth);        <---- This is the only change
GO

Ardından sorguyu yeniden çalıştırın:

SELECT *
FROM dbo.Table1
WHERE Dat = 'Test';

Şimdi SQL Server Sadece nereden satır bulmak gerekiyor çünkü olmayan tek bir kümelenmiş dizin aramak bkz Dat = 'Test'içinde IX_Table1değerini içerir endeksi, Othve değeri IDher olmayan otomatik mevcut olduğu sütuna (birincil anahtar), kümelenmiş dizin. Plan:

resim açıklamasını buraya girin


6

Anahtar araması, motorun almaya çalıştığınız tüm sütunları içermeyen bir dizin kullanmayı seçmesi nedeniyle oluşur. Dolayısıyla dizin select ve where deyimindeki sütunları kapsamaz.

Anahtar aramasını ortadan kaldırmak için eksik sütunları eklemeniz gerekir (anahtar aramasının Çıkış listesindeki sütunlar) = ProducerContactGuid, QuoteStatusID, PolicyTypeID ve ProducerLocationID veya başka bir yol, sorguyu bunun yerine kümelenmiş dizini kullanmaya zorlamaktır.

Bir tablodaki 27 Kümelenmemiş dizinin performans için kötü olabileceğini unutmayın. Bir güncelleme, ekleme veya silme çalıştırırken, SQL Server tüm dizinleri güncellemelidir. Bu fazladan çalışma performansı olumsuz etkileyebilir.


Ayrıca, çok fazla dizinin yürütme planının derlenmesini karıştırabileceğini ve optimal olmayan seçimlerle sonuçlanabileceğini unutmayın.
Lopsided

4

Bu sorguda yer alan veri hacminden bahsetmeyi unuttunuz. Ayrıca neden geçici bir tabloya ekliyorsunuz? Yalnızca görüntülemeniz gerekiyorsa, bir insert ifadesi çalıştırmayın.

Bu sorgunun amaçları doğrultusunda, tblQuoteskümelenmemiş 27 dizine gerek yoktur. 1 kümelenmiş dizin ve 5 kümelenmemiş dizin veya belki 6 kümelenmemiş indexex gerekir.

Bu sorgu şu sütunlardaki dizinleri ister:

QuoteStatusID
ProducerLocationID
ProducerContactGuid
EffectiveDate
LineGUID
OriginalQuoteGUID

Ayrıca aşağıdaki kodu fark ettim:

DATEDIFF(D, @EffDateFrom, tblQuotes.EffectiveDate) >= 0 AND 
DATEDIFF(D, @EffDateTo, tblQuotes.EffectiveDate) <= 0

olduğunu NON Sargableo dizinleri yararlanamazlar yani.

Bu kodu şu şekilde SARgabledeğiştirmek için:

tblQuotes.EffectiveDate >= @EffDateFrom 
AND  tblQuotes.EffectiveDate <= @EffDateFrom

Ana sorunuzu yanıtlamak için, "neden önemli bir Look up alıyorsunuz":

KEY Look upSorguda adı geçen sütunun bir kısmını bir kaplama dizininde bulunmadığı için alıyorsunuz .

Google'da Covering Indexveya hakkında çalışabilirsiniz Include index.

Örneğimde, tblQuotes.QuoteStatusID öğesinin Kümelenmemiş dizin olduğunu varsayalım, ayrıca DisplayStatus'u da kapsayabilir. Resultset içinde DisplayStatus'u istediğiniz için. Bir dizinde bulunmayan ve sonuç kümesinde bulunan herhangi bir sütun, bundan kaçınmak için kaplanabilir KEY Look Up or Bookmark lookup. Bu, dizin içeren bir örnektir:

create nonclustered index tblQuotes_QuoteStatusID 
on tblQuotes(QuoteStatusID)
include(DisplayStatus);

** Feragatname: ** Yukarıda hatırlıyorum sadece benim örneğim DisplayStatus analizden sonra diğer CI olmayanlar ile kaplanabilir.

Benzer şekilde, sorguda yer alan diğer tablolarda dizin ve kaplama dizini oluşturmanız gerekecektir.

Sen alıyorsanız Index SCANPlanınızdaki da.

Bunun nedeni, tabloda bir Dizin olmaması veya büyük miktarda veri olması durumunda, iyileştiricinin bir dizin araması yapmak yerine taramaya karar vermesi olabilir.

Bu da nedeniyle oluşabilir High cardinality. Hatalı birleştirme nedeniyle gerekenden daha fazla sayıda satır almak. Bu da düzeltilebilir.

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.