Meningkatkan kinerja kueri menggunakan IN ()

14

Saya punya pertanyaan SQL berikut:

SELECT
  Event.ID,
  Event.IATA,
  Device.Name,
  EventType.Description,
  Event.Data1,
  Event.Data2
  Event.PLCTimeStamp,
  Event.EventTypeID
FROM
  Event
INNER JOIN EventType ON EventType.ID = Event.EventTypeID
INNER JOIN Device ON Device.ID = Event.DeviceID
WHERE
  Event.EventTypeID IN (3, 30, 40, 41, 42, 46, 49, 50)
  AND Event.PLCTimeStamp BETWEEN '2011-01-28' AND '2011-01-29'
  AND Event.IATA LIKE '%0005836217%'
ORDER BY Event.ID;

Saya juga punya indeks di atas Eventmeja untuk kolom TimeStamp. Pemahaman saya adalah bahwa indeks ini tidak digunakan karena IN()pernyataan itu. Jadi pertanyaan saya adalah adakah cara untuk membuat indeks untuk IN()pernyataan khusus ini untuk mempercepat permintaan ini?

Saya juga mencoba menambahkan Event.EventTypeID IN (2, 5, 7, 8, 9, 14)sebagai filter untuk indeks aktif TimeStamp, tetapi ketika melihat rencana eksekusi sepertinya tidak menggunakan indeks ini. Setiap saran atau wawasan tentang ini akan sangat dihargai.

Di bawah ini adalah rencana grafis:

Rencana eksekusi

Dan di sini ada tautan ke file .sqlplan .

SandersKY
sumber
Bisakah kita melihat rencana eksekusi juga? :)
dezso
1
Dan tolong posting rencana eksekusi yang sebenarnya (tidak diperkirakan) dengan ekstensi .sqlplan. Kebanyakan orang hanya ingin memposting tangkapan layar dari rencana grafis, dan itu jauh kurang berguna.
Aaron Bertrand
OK saya menambahkan rencana eksekusi serta memperbarui permintaan SQL.
SandersKY
@SandersKY Sebaiknya sebaris file .sqlplan untuk menjaga semua yang terkait dengan pertanyaan di situs yang sama.
Trygve Laugstøl
1
@trygvis - Itu seringkali tidak mungkin dilakukan karena keterbatasan panjang pada posting. Shame stack exchange tidak mendukung lampiran posting hosting secara internal.
Martin Smith

Jawaban:

18

Diberikan tabel bentuk umum berikut:

CREATE TABLE Device 
(
    ID integer PRIMARY KEY
);

CREATE TABLE EventType
(
    ID integer PRIMARY KEY, 
    Name nvarchar(50) NOT NULL
);

CREATE TABLE [Event]
(
    ID integer PRIMARY KEY, 
    [TimeStamp] datetime NOT NULL, 
    EventTypeID integer NOT NULL REFERENCES EventType, 
    DeviceID integer NOT NULL REFERENCES Device
);

Indeks berikut bermanfaat:

CREATE INDEX f1 
ON [Event] ([TimeStamp], EventTypeID) 
INCLUDE (DeviceID)
WHERE EventTypeID IN (2, 5, 7, 8, 9, 14);

Untuk kueri:

SELECT
  [Event].ID,
  [Event].[TimeStamp],
  EventType.Name,
  Device.ID
FROM
  [Event]
INNER JOIN EventType ON EventType.ID = [Event].EventTypeID
INNER JOIN Device ON Device.ID = [Event].DeviceID
WHERE
  [Event].[TimeStamp] BETWEEN '2011-01-28' AND '2011-01-29'
  AND Event.EventTypeID IN (2, 5, 7, 8, 9, 14);

Filter memenuhi ANDpersyaratan klausa, kunci pertama indeks memungkinkan pencarian [TimeStamp]untuk difilter EventTypeIDsdan termasuk DeviceIDkolom membuat indeks meliputi (karena DeviceIDdiperlukan untuk bergabung ke Devicetabel).

Rencana selesai

Kunci kedua dari indeks - EventTypeIDtidak sepenuhnya diperlukan (bisa juga berupa INCLUDEdkolom); Saya telah memasukkannya ke dalam kunci karena alasan yang disebutkan di sini . Secara umum, saya menyarankan orang untuk setidaknya INCLUDEkolom dari WHEREklausa indeks yang difilter .


Berdasarkan permintaan yang diperbarui dan rencana eksekusi dalam pertanyaan, saya setuju bahwa indeks yang lebih umum yang disarankan oleh SSMS kemungkinan adalah pilihan yang lebih baik di sini, kecuali daftar yang difilter EventTypeIDsbersifat statis karena Aaron juga menyebutkan dalam jawabannya:

CREATE TABLE Device 
(
    ID integer PRIMARY KEY,
    Name nvarchar(50) NOT NULL UNIQUE
);

CREATE TABLE EventType
(
    ID integer PRIMARY KEY, 
    Name nvarchar(20) NOT NULL UNIQUE,
    [Description] nvarchar(100) NOT NULL
);

CREATE TABLE [Event]
(
    ID integer PRIMARY KEY, 
    PLCTimeStamp datetime NOT NULL,
    EventTypeID integer NOT NULL REFERENCES EventType, 
    DeviceID integer NOT NULL REFERENCES Device,
    IATA varchar(50) NOT NULL,
    Data1 integer NULL,
    Data2 integer NULL,
);

Indeks yang disarankan (nyatakan unik jika sesuai):

CREATE UNIQUE INDEX uq1
ON [Event]
    (EventTypeID, PLCTimeStamp)
INCLUDE 
    (DeviceID, IATA, Data1, Data2, ID);

Informasi kardinalitas dari rencana eksekusi (sintaksis tidak berdokumen, jangan gunakan dalam sistem produksi):

UPDATE STATISTICS dbo.Event WITH ROWCOUNT = 4042700, PAGECOUNT = 400000;
UPDATE STATISTICS dbo.EventType WITH ROWCOUNT = 22, PAGECOUNT = 1;
UPDATE STATISTICS dbo.Device WITH ROWCOUNT = 2806, PAGECOUNT = 28;

Kueri yang diperbarui (mengulangi INdaftar untuk EventTypetabel membantu pengoptimal dalam kasus khusus ini):

SELECT
  Event.ID,
  Event.IATA,
  Device.Name,
  EventType.Description,
  Event.Data1,
  Event.Data2,
  Event.PLCTimeStamp,
  Event.EventTypeID
FROM
  Event
INNER JOIN EventType ON EventType.ID = Event.EventTypeID
INNER JOIN Device ON Device.ID = Event.DeviceID
WHERE
  Event.EventTypeID IN (3, 30, 40, 41, 42, 46, 49, 50)
  AND EventType.ID IN (3, 30, 40, 41, 42, 46, 49, 50)
  AND Event.PLCTimeStamp BETWEEN '2011-01-28' AND '2011-01-29'
  AND Event.IATA LIKE '%0005836217%'
ORDER BY Event.ID;

Perkiraan rencana eksekusi:

Rencana kedua

Paket yang Anda dapatkan kemungkinan akan berbeda karena saya menggunakan statistik dugaan. Poin umumnya adalah memberikan pengoptimal sebanyak mungkin informasi, dan memberikan metode akses (indeks) yang efisien pada [Event]tabel 4-juta baris .

Paul White 9
sumber
8

Sebagian besar biaya adalah scan indeks berkerumun, dan kecuali tabel ini benar-benar luas atau Anda tidak benar-benar membutuhkan semua kolom dalam output, saya percaya SQL Server bahwa ini adalah jalur optimal dalam skenario saat ini dengan tidak ada lagi yang berubah . Itu memang menggunakan pemindaian rentang (diberi label sebagai pencarian CI) untuk mempersempit rentang baris yang diminati, tetapi karena outputnya masih akan memerlukan pencarian atau pemindaian CI bahkan dengan indeks yang difilter yang Anda buat. ditargetkan pada kisaran ini, dan bahkan dalam kasus itu pemindaian CI mungkin masih termurah (atau setidaknya SQL Server memperkirakannya seperti itu).

Rencana eksekusi memberi tahu Anda bahwa indeks ini akan berguna:

CREATE NONCLUSTERED INDEX ix_EventTypeID_PLCTimeStamp_WithIncludes
  ON [dbo].[Event] ([EventTypeID],[PLCTimeStamp])
  INCLUDE ([ID],[DeviceID],[Data1],[Data2],[IATA]);

Meskipun tergantung pada kemiringan data Anda, mungkin lebih baik sebaliknya, misalnya:

CREATE NONCLUSTERED INDEX ix_PLCTimeStamp_EventTypeID_WithIncludes
  ON [dbo].[Event] ([PLCTimeStamp],[EventTypeID])
  INCLUDE ([ID],[DeviceID],[Data1],[Data2],[IATA]);

Tapi saya akan menguji keduanya untuk memastikan mana yang lebih baik, jika salah satu - perbedaan antara salah satu indeks dan apa yang Anda miliki sekarang mungkin hanya marjinal (terlalu banyak variabel untuk kita ketahui) dan Anda harus memperhitungkan bahwa tambahan indeks membutuhkan pemeliharaan tambahan, dan ini dapat mempengaruhi operasi DML Anda (masukkan / perbarui / hapus). Anda juga dapat mempertimbangkan untuk memasukkan kriteria filter dalam indeks ini seperti yang disarankan oleh @SQLKiwi , tetapi hanya jika itu adalah serangkaian nilai EventTypeID yang sering Anda cari. Jika itu mengatur perubahan dari waktu ke waktu, maka indeks yang difilter hanya akan berguna untuk permintaan khusus ini.

Dengan jumlah baris yang rendah, saya harus bertanya-tanya seberapa buruk kinerjanya saat ini? Kueri ini mengembalikan 3 baris (tetapi tidak ada indikasi berapa banyak baris yang ditolaknya). Berapa banyak baris dalam tabel?

Aaron Bertrand
sumber
4

Saya baru tahu bahwa SQL Server 2008 R2 benar-benar membuat saran indeks ketika saya menjalankan rencana eksekusi. Indeks yang disarankan ini membuat kueri berjalan sekitar 90% lebih cepat.

Indeks yang disarankan adalah sebagai berikut:

CREATE NONCLUSTERED INDEX [INDEX_spBagSearch] ON [dbo].[Event] 
(
    [EventTypeID] ASC,
    [PLCTimeStamp] ASC
)
INCLUDE ( [ID],
[DeviceID],
[Data1],
[Data2],
[IATA]) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO
SandersKY
sumber