Indeks mencari jauh lebih lambat dengan kondisi OR dibandingkan dengan SELECT terpisah

8

Berdasarkan pertanyaan-pertanyaan ini dan jawaban yang diberikan:

SQL 2008 Server - kehilangan kinerja mungkin terhubung dengan tabel yang sangat besar

Tabel besar dengan data historis mengalokasikan terlalu banyak SQL Server 2008 Std. memori - kehilangan kinerja untuk database lain

Saya memiliki tabel dalam database SupervisionP yang didefinisikan seperti ini:

CREATE TABLE [dbo].[PenData](
    [IDUkazatel] [smallint] NOT NULL,
    [Cas] [datetime2](0) NOT NULL,
    [Hodnota] [real] NULL,
    [HodnotaMax] [real] NULL,
    [HodnotaMin] [real] NULL,
 CONSTRAINT [PK_Data] PRIMARY KEY CLUSTERED 
(
    [IDUkazatel] ASC,
    [Cas] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [dbo].[PenData]  WITH NOCHECK ADD  CONSTRAINT [FK_Data_Ukazatel] FOREIGN KEY([IDUkazatel])
REFERENCES [dbo].[Ukazatel] ([IDUkazatel])

ALTER TABLE [dbo].[PenData] CHECK CONSTRAINT [FK_Data_Ukazatel]

Ini berisi cca 211 juta baris.

Saya menjalankan pernyataan berikut:

DECLARE @t1 DATETIME;
DECLARE @t2 DATETIME;

SET @t1 = GETDATE();
SELECT min(cas) from PenData p WHERE IDUkazatel=24
SELECT min(cas) from PenData p WHERE IDUkazatel=25
SET @t2 = GETDATE();
SELECT DATEDIFF(millisecond,@t1,@t2) AS elapsed_ms;


SET @t1 = GETDATE();
SELECT min(cas) from PenData p WHERE IDUkazatel=24 OR IDUkazatel=25 
SET @t2 = GETDATE();
SELECT DATEDIFF(millisecond,@t1,@t2) AS elapsed_ms;

Hasilnya ditunjukkan di sini:

Rencana eksekusi

SELECT ketiga juga memuat lebih banyak data ke dalam cache memori SQL Server.

Mengapa SELECT ketiga jauh lebih lambat (8,5 detik) daripada dua SELECT pertama (16 ms)? Bagaimana saya dapat meningkatkan kinerja pemilihan ketiga dengan OR? Saya ingin menjalankan perintah SQL berikut, tetapi bagi saya tampaknya membuat kursor dan menjalankan kueri terpisah jauh lebih cepat daripada pilihan tunggal dalam kasus ini.

 SELECT MIN(cas) from PenData p WHERE IDUkazatel IN (SELECT IDUkazatel FROM  ...)

EDIT

Seperti yang disarankan David, saya melayang di atas panah gemuk:

FatArrow

Vojtěch Dohnal
sumber

Jawaban:

11

Untuk dua pertanyaan pertama yang harus dilakukan adalah memindai indeks berkerumun ke entri pertama untuk nilai IDUkazatel- karena urutan indeks yang baris akan menjadi nilai terendah untuk cas untuk nilai IDUkazatel.

Dalam kueri kedua optimasi ini bukan nilai dan mungkin mencari ke baris pertama untuk IDUkazatel=24kemudian memindai indeks sampai baris terakhir dengan IDUkazatel=25untuk menemukan nilai minimum dari cassemua baris tersebut.

Jika Anda mengarahkan panah gemuk itu, Anda akan melihatnya sedang membaca banyak baris (tentu saja semua untuk 24, mungkin semua untuk 25 juga), sedangkan panah tipis dalam output rencana untuk dua lainnya menunjukkan toptindakan yang menyebabkan hanya pertimbangkan satu baris.

Anda dapat mencoba menjalankan setiap permintaan dan kemudian mendapatkan minimum untuk minimum yang ditemukan:

SELECT MIN(cas)
FROM   (
        SELECT cas=MIN(cas) FROM PenData p WHERE p.IDUkazatel = 24
        UNION ALL
        SELECT cas=MIN(cas) FROM PenData p WHERE p.IDUkazatel = 25
    ) AS minimums

Yang mengatakan, sepertinya Anda memiliki tabel dengan IDUkazatelnilai daripada ORklausa eksplisit . Kode di bawah ini akan berfungsi dengan pengaturan itu, cukup ganti nama tabel @Tdengan nama tabel yang berisi IDUkazatelnilai:

SELECT 
    MinCas = MIN(CA.PartialMinimum)
FROM @T AS T
CROSS APPLY 
(
    SELECT 
        PartialMinimum = MIN(PD.Cas)
    FROM dbo.PenData AS PD
    WHERE 
        PD.IDUkazatel = T.IDUkazatel
) AS CA;

Di dunia yang ideal, pengoptimal permintaan SQL Server akan melakukan penulisan ulang ini untuk Anda, tetapi tidak selalu mempertimbangkan opsi ini hari ini.

David Spillett
sumber
Anda dapat menulis ulang yang terakhir tanpa tabel turunan SELECT TOP (1) min_cas=MIN(CAS) ... ORDER BY min_cas;(tapi saya rasa rencananya akan sama dengan milik Anda.)
ypercubeᵀᴹ