Mengapa permintaan EXISTS saya melakukan pemindaian indeks alih-alih pencarian indeks?

15

Saya sedang berupaya mengoptimalkan beberapa pertanyaan.

Untuk kueri di bawah ini,

SET STATISTICS IO ON;
DECLARE @OrderStartDate DATETIME2 = '27 feb 2016';
DECLARE @OrderEndDate  DATETIME2 = '28 feb 2016';

SELECT  o.strBxOrderNo
        , o.sintOrderStatusID
        , o.sintOrderChannelID
        , o.sintOrderTypeID
        , o.sdtmOrdCreated
        , o.sintMarketID
        , o.strOrderKey
        , o.strOfferCode
        , o.strCurrencyCode
        , o.decBCShipFullPrice
        , o.decBCShipFinal
        , o.decBCShipTax
        , o.decBCTotalAmount
        , o.decWrittenTotalAmount
        , o.decBCWrittenTotalAmount
        , o.decBCShipOfferDisc
        , o.decBCShipOverride
        , o.decTotalAmount
        , o.decShipTax
        , o.decShipFinal
        , o.decShipOverride
        , o.decShipOfferDisc
        , o.decShipFullPrice
        , o.lngAccountParticipantID
        , CONVERT(DATE, o.sdtmOrdCreated, 120) as OrderCreatedDateConverted
FROM    tablebackups.dbo.tblBOrder o
WHERE   o.sdtmOrdCreated >= @OrderStartDate
        AND o.sdtmOrdCreated < @OrderEndDate
        AND EXISTS  (
            SELECT  *
            FROM    tablebackups.dbo.tblBOrderItem oi 
            WHERE   oi.strBxOrderNo = o.strBxOrderNo
            AND     oi.decCatItemPrice > 0
        )
OPTION (RECOMPILE);

Saya telah membuat indeks FILTERED berikut:

-- table dbo.tblBorderItem
CREATE NONCLUSTERED INDEX IX_tblBOrderItem_decCatItemPrice_INCL 
ON dbo.tblBorderItem 
( 
     strBxOrderNo ASC
    , sintOrderSeqNo ASC
    , decCatItemPrice   
)   
INCLUDE 
(
    blnChargeShipping
    , decBCCatItemPrice
    , decBCCostPrice
    , decBCFinalPrice
    , decBCOfferDiscount
    , decBCOverrideDiscount
    , decBCTaxAmount
    , decCostPrice
    , decFinalPrice
    , decOfferDiscount
    , decOverrideDiscount
    , decTaxAmount
    , decWasPrice
    , dtmOrdItemCreated
    , sintOrderItemStatusId
    , sintOrderItemType
    , sintQuantity
    , strItemNo
)  
WHERE decCatItemPrice > 0 
WITH (DROP_EXISTING = ON, FILLFACTOR = 95);

Indeks ini tidak hanya digunakan untuk kueri ini khususnya, ada permintaan lain yang menggunakan indeks yang sama ini, oleh karena itu kolom TERMASUK.

Khusus untuk kueri ini, saya hanya ingin memeriksa (ADA) jika ada pesanan di mana saja decCatItemPrice > 0.

SQL Server sedang melakukan pemindaian indeks seperti yang Anda lihat pada gambar di bawah ini.

  • Statistik baru saja diperbarui.
  • Tabel item memiliki 41.208 baris dalam pengujian.

Harap dicatat, saya tidak memilih kolom apa pun dari tabel item.

Tabel item ini memiliki 164.309.397 siaran langsung. Saya ingin menghindari pemindaian di sana.

pertanyaan:

Mengapa SQL Server tidak melakukan pencarian indeks?

Apakah ada faktor / hal lain yang harus saya pertimbangkan untuk meningkatkan kueri ini?

(4537 row(s) affected) Table 'tblBorder'. Scan count 1, logical reads
116, physical reads 0, read-ahead reads 0, lob logical reads 0, lob
physical reads 0, lob read-ahead reads 0. Table 'tblBorderItem'. Scan
count 1, logical reads 689, physical reads 0, read-ahead reads 0, lob
logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

ini adalah definisi dan indeks pada tabel tblBorderItem

    IF OBJECT_ID('[dbo].[tblBorderItem]') IS NOT NULL 
    DROP TABLE [dbo].[tblBorderItem] 
    GO
    CREATE TABLE [dbo].[tblBorderItem] ( 
    [strBxOrderNo]                VARCHAR(20)                      NOT NULL,
    [sintOrderSeqNo]              SMALLINT                         NOT NULL,
    [sintOrderItemStatusId]       SMALLINT                         NOT NULL,
    [sintNameStructureID]         SMALLINT                         NOT NULL,
    [strItemNo]                   VARCHAR(20)                      NOT NULL,
    [sintQuantity]                SMALLINT                         NOT NULL,
    [strCurrencyCode]             VARCHAR(3)                       NOT NULL,
    [decCostPrice]                DECIMAL(18,4)                    NOT NULL,
    [decCatItemPrice]             DECIMAL(18,2)                    NOT NULL,
    [decOfferDiscount]            DECIMAL(18,2)                    NOT NULL,
    [decOverrideDiscount]         DECIMAL(18,2)                    NOT NULL,
    [decFinalPrice]               DECIMAL(18,2)                    NOT NULL,
    [decTaxAmount]                DECIMAL(18,2)                    NOT NULL,
    [strBCCurrencyCode]           VARCHAR(3)                       NOT NULL,
    [decBCCostPrice]              DECIMAL(18,4)                    NOT NULL,
    [decBCCatItemPrice]           DECIMAL(18,4)                    NOT NULL,
    [decBCOfferDiscount]          DECIMAL(18,4)                    NOT NULL,
    [decBCOverrideDiscount]       DECIMAL(18,4)                    NOT NULL,
    [decBCFinalPrice]             DECIMAL(18,4)                    NOT NULL,
    [decBCTaxAmount]              DECIMAL(18,4)                    NOT NULL,
    [dtmOrdItemCreated]           DATETIME                         NOT NULL,
    [blnChargeShipping]           BIT                              NOT NULL,
    [lngTimeOfOrderQtyOnHand]     INT                                  NULL,
    [sdtmTimeOfOrderDueDate]      SMALLDATETIME                        NULL,
    [lngProdSetSeqNo]             INT                                  NULL,
    [lngProdRelationId]           INT                                  NULL,
    [lngProdRelationMemberId]     INT                                  NULL,
    [decWasPrice]                 DECIMAL(18,2)                        NULL,
    [sintOrderItemType]           SMALLINT                             NULL,
    [tsRowVersion]                TIMESTAMP                            NULL,
    [sdtmOrderItemStatusUpdated]  SMALLDATETIME                        NULL,
    CONSTRAINT   [PK_tblBOrderItem]  
PRIMARY KEY CLUSTERED    
([strBxOrderNo] asc, [sintOrderSeqNo] asc) 
WITH FILLFACTOR = 100)

    GO

    CREATE NONCLUSTERED INDEX 
    [IX_tblBOrderItem__dtmOrdItemCreated] 
       ON [dbo].[tblBorderItem] ([dtmOrdItemCreated] asc)
       WITH FILLFACTOR = 100


    CREATE NONCLUSTERED INDEX [IX_tblBOrderItem__sintOrderItemStatusId] 
       ON [dbo].[tblBorderItem] ([sintOrderItemStatusId] asc)
       INCLUDE ([sdtmOrderItemStatusUpdated], 
    [sintOrderSeqNo], [strBxOrderNo], [strItemNo])
       WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX [IX_tblBOrderItem__
sintOrderItemStatusId_decFinalPrice_
sdtmOrderItemStatusUpdated_
include_strBxOrderNo] 
   ON [dbo].[tblBorderItem] 
([sintOrderItemStatusId] asc, 
 [decFinalPrice] asc, 
 [sdtmOrderItemStatusUpdated] asc)
   INCLUDE ([strBxOrderNo])
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX [IX_tblBOrderItem__strBxOrderNo] 
   ON [dbo].[tblBorderItem] 
([strBxOrderNo] asc)
   WITH FILLFACTOR = 100


CREATE NONCLUSTERED INDEX [IX_tblBOrderItem__strItemNo] 
   ON [dbo].[tblBorderItem] ([strItemNo] asc)
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX 
[IX_tblBOrderItem_decCatItemPrice_INCL] 
   ON [dbo].[tblBorderItem] 
([strBxOrderNo] asc, [sintOrderSeqNo] asc, [decCatItemPrice] asc)
   INCLUDE ([blnChargeShipping], 
[decBCCatItemPrice], [decBCCostPrice], [decBCFinalPrice], 
[decBCOfferDiscount], [decBCOverrideDiscount], 
[decBCTaxAmount], [decCostPrice], [decFinalPrice], 
[decOfferDiscount], [decOverrideDiscount], 
[decTaxAmount], [decWasPrice], [dtmOrdItemCreated], 
[sintOrderItemStatusId], [sintOrderItemType], 
[sintQuantity], [strItemNo])
   WHERE ([decCatItemPrice]>(0))
   WITH FILLFACTOR = 95

ini adalah definisi dan indeks pada tblBorder tabel

IF OBJECT_ID('[dbo].[tblBorder]') IS NOT NULL 
DROP TABLE [dbo].[tblBorder] 
GO
CREATE TABLE [dbo].[tblBorder] ( 
[strBxOrderNo]                VARCHAR(20)                      NOT NULL,
[uidOrderUniqueID]            UNIQUEIDENTIFIER                 NOT NULL,
[sintOrderStatusID]           SMALLINT                         NOT NULL,
[sintOrderChannelID]          SMALLINT                         NOT NULL,
[sintOrderTypeID]             SMALLINT                         NOT NULL,
[blnIsBasket]                 BIT                              NOT NULL,
[sdtmOrdCreated]              SMALLDATETIME                    NOT NULL,
[sintMarketID]                SMALLINT                         NOT NULL,
[strOrderKey]                 VARCHAR(20)                      NOT NULL,
[strOfferCode]                VARCHAR(20)                      NOT NULL,
[lngShippedToParticipantID]   INT                              NOT NULL,
[lngOrderedByParticipantID]   INT                              NOT NULL,
[lngShipToAddressID]          INT                              NOT NULL,
[lngAccountAddressID]         INT                              NOT NULL,
[lngAccountParticipantID]     INT                              NOT NULL,
[lngOrderedByAddressID]       INT                              NOT NULL,
[lngOrderTakenBy]             INT                              NOT NULL,
[strCurrencyCode]             VARCHAR(3)                       NOT NULL,
[decShipFullPrice]            DECIMAL(18,2)                    NOT NULL,
[decShipOfferDisc]            DECIMAL(18,2)                    NOT NULL,
[decShipOverride]             DECIMAL(18,2)                    NOT NULL,
[decShipFinal]                DECIMAL(18,2)                    NOT NULL,
[decShipTax]                  DECIMAL(18,2)                    NOT NULL,
[strBCCurrencyCode]           VARCHAR(3)                       NOT NULL,
[decBCShipFullPrice]          DECIMAL(18,4)                    NOT NULL,
[decBCShipOfferDisc]          DECIMAL(18,4)                    NOT NULL,
[decBCShipOverride]           DECIMAL(18,4)                    NOT NULL,
[decBCShipFinal]              DECIMAL(18,4)                    NOT NULL,
[decBCShipTax]                DECIMAL(18,4)                    NOT NULL,
[decTotalAmount]              DECIMAL(18,2)                    NOT NULL,
[decBCTotalAmount]            DECIMAL(18,4)                    NOT NULL,
[decWrittenTotalAmount]       DECIMAL(18,2)                        NULL,
[decBCWrittenTotalAmount]     DECIMAL(18,4)                        NULL,
[blnProRataShipping]          BIT                              NOT NULL,
[blnChargeWithFirstShipment]  BIT                              NOT NULL,
[sintShippingServiceLevelID]  SMALLINT                         NOT NULL,
[sintShippingMethodID]        SMALLINT                         NOT NULL,
[sdtmDoNotShipUntil]          SMALLDATETIME                        NULL,
[blnHoldUntilComplete]        BIT                              NOT NULL,
[tsRowVersion]                TIMESTAMP                            NULL,
CONSTRAINT   [PK_tblBOrder]  
PRIMARY KEY CLUSTERED    
([strBxOrderNo] asc) WITH FILLFACTOR = 100)

GO

CREATE NONCLUSTERED INDEX 
[IX_tblBOrder__lngAccountAddressID] 
   ON [dbo].[tblBorder] 
   ([lngAccountAddressID] asc, [sintOrderStatusID] asc)
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX 
[IX_tblBOrder__lngAccountParticipantID] 
   ON [dbo].[tblBorder] 
   ([lngAccountParticipantID] asc)
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX 
[IX_tblBOrder__lngOrderedByAddressID] 
   ON [dbo].[tblBorder] 
   ([lngOrderedByAddressID] asc, [sintOrderStatusID] asc)
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX 
[IX_tblBOrder__lngOrderedByParticipantID] 
   ON [dbo].[tblBorder] ([lngOrderedByParticipantID] asc)
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX 
[IX_tblBOrder__lngShippedToParticipantID] 
   ON [dbo].[tblBorder] 
   ([lngShippedToParticipantID] asc)
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX 
[IX_tblBOrder__lngShipToAddressID] 
   ON [dbo].[tblBorder] 
   ([lngShipToAddressID] asc, [sintOrderStatusID] asc)
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX 
[IX_tblBOrder__sdtmOrdCreated_sintMarketID__include_strBxOrderNo] 
   ON [dbo].[tblBorder] 
   ([sdtmOrdCreated] asc, [sintMarketID] asc)
   INCLUDE ([strBxOrderNo])
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX 
[IX_tblBOrder_sdtmOrdCreated_INCL] 
   ON [dbo].[tblBorder] 
   ([sdtmOrdCreated] asc)
   INCLUDE ([decBCShipFinal], [decBCShipFullPrice], 
            [decBCShipOfferDisc], [decBCShipOverride], 
            [decBCShipTax], [decBCTotalAmount], [decBCWrittenTotalAmount], 
            [decShipFinal], [decShipFullPrice], [decShipOfferDisc], 
            [decShipOverride], [decShipTax], [decTotalAmount], 
            [decWrittenTotalAmount], [lngAccountParticipantID], 
            [lngOrderedByParticipantID], [sintMarketID], 
            [sintOrderChannelID], [sintOrderStatusID], 
            [sintOrderTypeID], [strBxOrderNo], [strCurrencyCode], 
            [strOfferCode], [strOrderKey])
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED 
INDEX [IX_tblBOrder_sintMarketID_sdtmOrdCreated] 
   ON [dbo].[tblBorder] 
   ([sintMarketID] asc, [sdtmOrdCreated] asc)
   INCLUDE ([sintOrderChannelID], [strBxOrderNo])
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED 
INDEX [IX_tblBOrder__sintOrderChannelID_sdtmOrdCreated_INCL] 
   ON [dbo].[tblBorder] 
   ([sintOrderChannelID] asc, [sdtmOrdCreated] asc)
   INCLUDE ([decBCShipFinal], [decBCShipFullPrice], 
   [decBCShipTax], [decShipFinal], [decShipFullPrice], 
   [decShipTax], [lngAccountParticipantID], [sintMarketID], 
   [sintOrderTypeID], [strBxOrderNo], 
   [strCurrencyCode], [strOrderKey])
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX [IX_tblBOrder_strBxOrderNo_sdtmOrdCreated_incl] 
   ON [dbo].[tblBorder] ([strBxOrderNo] asc, 
   [sdtmOrdCreated] asc)
   INCLUDE ([sintOrderChannelID], [sintOrderTypeID], [sintMarketID], 
   [strOrderKey], [lngAccountParticipantID], [strCurrencyCode], 
   [decShipFullPrice], [decShipFinal], [decShipTax], 
   [decBCShipFullPrice], [decBCShipFinal], 
   [decBCShipTax])

Kesimpulan

Saya menerapkan indeks saya pada sistem LIVE, dan memperbarui prosedur tersimpan saya untuk menggunakan SMALLDATETIME, agar sesuai dengan tipe data dalam database untuk kolom yang terlibat.

Setelah itu, ketika melihat paket permintaan saya melihat gambar di bawah ini:

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

persis seperti yang saya inginkan.

Saya pikir optimizer permintaan dalam hal ini melakukan pekerjaan yang baik untuk mendapatkan rencana permintaan terbaik di kedua lingkungan, dan saya senang saya tidak menambahkan petunjuk kueri.

Saya belajar dengan 3 jawaban yang diposting. terima kasih kepada Max Vernon , Paul White dan Daniel Hutmacher atas jawaban mereka.

Marcello Miorelli
sumber

Jawaban:

15

Jika Anda ingin hasil yang baik dari pengoptimal kueri, sebaiknya berhati-hati tentang tipe data .

Variabel Anda diketik sebagai datetime2 :

DECLARE @OrderStartDate datetime2 = '27 feb 2016';
DECLARE @OrderEndDate  datetime2 = '28 feb 2016';

Tapi kolom ini dibandingkan dengan diketik smalldatetime (sebagai SDTM awalan menyarankan!):

[sdtmOrdCreated] SMALLDATETIME NOT NULL

Ketidakcocokan jenis mempersulit pengoptimal untuk mengerjakan perkiraan kardinalitas yang dihasilkan melalui konversi jenis , seperti yang ditunjukkan dalam paket eksekusi xml:

<ScalarOperator ScalarString="GetRangeWithMismatchedTypes([@OrderStartDate],NULL,(22))">
<ScalarOperator ScalarString="GetRangeWithMismatchedTypes(NULL,[@OrderEndDate],(10))">

Estimasi saat ini mungkin akurat atau tidak (mungkin tidak). Memperbaiki ketidakcocokan jenis mungkin atau mungkin tidak sepenuhnya menyelesaikan masalah pemilihan paket Anda, tetapi itu adalah hal pertama (mudah!) Yang akan saya perbaiki sebelum melihat lebih dalam ke masalah:

DECLARE @OrderStartDate smalldatetime = CONVERT(smalldatetime, '20160227', 112);
DECLARE @OrderEndDate smalldatetime = CONVERT(smalldatetime, '20160228', 112);

Selalu periksa keakuratan perkiraan kardinalitas, dan alasan perbedaan apa pun sebelum memutuskan untuk menulis ulang kueri atau menggunakan petunjuk.

Lihat artikel SQLblog.com saya, "Mencari Dinamis dan Konversi Tersirat Tersembunyi" untuk detail lebih lanjut tentang pencarian dinamis.

Pembaruan: Memperbaiki tipe data memberi Anda rencana pencarian yang Anda inginkan. Kesalahan estimasi kardinalitas yang disebabkan oleh konversi tipe sebelum memberi Anda paket lebih lambat.

Paul White Reinstate Monica
sumber
11

SQL Server melakukan pemindaian indeks karena dianggap lebih murah daripada mencari untuk setiap baris yang diperlukan. Kemungkinan besar, SQL Server sudah benar, mengingat pilihan yang ada di pengaturan Anda.

Sadarilah SQL Server mungkin benar-benar melakukan pemindaian rentang pada indeks, sebagai lawan memindai seluruh indeks.

Jika Anda memberikan DDL untuk kedua tabel, bersama dengan indeks lain yang Anda miliki, kami mungkin dapat membantu Anda menjadikan sumber daya ini jauh lebih sedikit intensif.

Sebagai catatan, jangan pernah menggunakan literal tanggal seperti itu. Dari pada:

DECLARE @OrderStartDate DATETIME2 = '27 feb 2016';
DECLARE @OrderEndDate  DATETIME2 = '28 feb 2016';

Gunakan ini:

DECLARE @OrderStartDate DATETIME2 = '2016-02-27T00:00:00.0000';
DECLARE @OrderEndDate  DATETIME2 = '2016-02-28T00:00:00.0000';

Posting Harun dapat membantu memperjelas hal itu.

Max Vernon
sumber
6

Untuk menambah jawaban Max, saya mungkin akan mencoba untuk membagi kueri Anda menjadi dua bagian:

DECLARE @OrderStartDate DATETIME2 = {d '2016-02-27'};
DECLARE @OrderEndDate   DATETIME2 = {d '2016-02-28'};

--- Work variable declarations:
DECLARE @minOrderNo varchar(20), @maxOrderNo varchar(20);

--- Find the lowest and highest order number respectively for
--- your date range:
SELECT @minOrderNo=MIN(strBxOrderNo),
       @maxOrderNo=MAX(strBxOrderNo)
FROM    dbo.tblBOrder o
WHERE   o.sdtmOrdCreated >= @OrderStartDate AND
        o.sdtmOrdCreated <  @OrderEndDate;

--- Join orders and order items on their respective clustering keys.
SELECT    o.strBxOrderNo
        , o.sintOrderStatusID
        , o.sintOrderChannelID
        , o.sintOrderTypeID
        , o.sdtmOrdCreated
        , o.sintMarketID
        , o.strOrderKey
        , o.strOfferCode
        , o.strCurrencyCode
        , o.decBCShipFullPrice
        , o.decBCShipFinal
        , o.decBCShipTax
        , o.decBCTotalAmount
        , o.decWrittenTotalAmount
        , o.decBCWrittenTotalAmount
        , o.decBCShipOfferDisc
        , o.decBCShipOverride
        , o.decTotalAmount
        , o.decShipTax
        , o.decShipFinal
        , o.decShipOverride
        , o.decShipOfferDisc
        , o.decShipFullPrice
        , o.lngAccountParticipantID
        , CONVERT(DATE, o.sdtmOrdCreated, 120) as OrderCreatedDateConverted
FROM dbo.tblBOrder AS o
INNER /*MERGE*/ JOIN dbo.tblBOrderItem AS oi ON
    o.strBxOrderNo>=@minOrderNo AND      --- OrderNo filter on "orders"
    o.strBxOrderNo<=@maxOrderNo AND
    oi.strBxOrderNo=o.strBxOrderNo AND   --- Equijoin
    oi.strBxOrderNo>=@minOrderNo AND     --- OrderNo filter on "order items"
    oi.strBxOrderNo<=@maxOrderNo AND
    oi.decCatItemPrice > 0               --- Item price filter on "order items"
OPTION (RECOMPILE);

Kueri ini akan (a) menghilangkan operator Sortir (yang mahal karena memblokir dan membutuhkan memori), (b) membuat Gabung Gabung (yang dapat Anda paksakan dengan petunjuk gabung, tetapi seharusnya terjadi secara otomatis dengan data yang cukup ). Sebagai bonus, itu juga akan (c) menghilangkan Pemindaian Indeks.

Secara keseluruhan, kueri MIN / MAX menggunakan indeks yang sangat optimal pada tabel Pesanan untuk mengidentifikasi berbagai nomor pesanan (termasuk dalam kunci pengelompokan) dari indeks yang tidak dikelompokkan di kolom tanggal:

MIN / MAX rencana permintaan

Kemudian, Anda bisa Gabung Bergabung dengan dua tabel di indeks berkerumun masing-masing:

Gabung Bergabung dengan pesanan dan item pesanan

Jelas, saya tidak memiliki data Anda untuk diuji, tetapi saya membayangkan ini harus menjadi solusi yang benar-benar berkinerja baik.

Daniel Hutmacher
sumber
+1 Saya sangat menyukai cara berpikir di sini. Itu tidak berhasil untuk skenario kasus ini pada khususnya tetapi saya mendapat ide.
Marcello Miorelli