Saya punya pertanyaan yang cukup sederhana
SELECT TOP 1 dc.DOCUMENT_ID,
dc.COPIES,
dc.REQUESTOR,
dc.D_ID,
cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER
Itu memberi saya kinerja yang mengerikan (seperti tidak pernah repot-repot menunggu sampai selesai). Rencana kueri terlihat seperti ini:
Namun jika saya menghapus TOP 1
saya mendapatkan paket yang terlihat seperti ini dan itu berjalan dalam 1-2 detik:
PK & pengindeksan yang benar di bawah ini.
Fakta bahwa TOP 1
perubahan rencana kueri tidak mengejutkan saya, saya hanya sedikit terkejut bahwa itu membuatnya jauh lebih buruk.
Catatan: Saya sudah membaca hasil dari posting ini dan memahami konsep Row Goal
dll. Yang saya ingin tahu adalah bagaimana saya bisa mengubah kueri sehingga menggunakan rencana yang lebih baik. Saat ini saya sedang membuang data ke tabel temp lalu menarik baris pertama darinya. Saya bertanya-tanya apakah ada metode yang lebih baik.
Edit Untuk orang-orang yang membaca ini setelah fakta di sini ada beberapa informasi tambahan.
- Document_Queue - PK / CI adalah D_ID dan memiliki ~ 5k baris.
- Correspondence_Journal - PK / CI adalah FILE_NUMBER, CORRESPONDENCE_ID dan memiliki ~ 1,4 juta baris.
Ketika saya mulai, tidak ada indeks lain. Saya berakhir dengan satu di Correspondence_Journal (Document_Id, File_Number)
sumber
DOCUMENT_ID
hubungan antara dua tabel (atau apakah setiap catatanCORRESPONDENCE_JOURNAL
memiliki catatan yang cocok diDOCUMENT_QUEUE
)?Jawaban:
Coba paksakan hash gabung *
Pengoptimal mungkin mengira loop akan lebih baik dengan top 1 dan itu masuk akal tetapi pada kenyataannya itu tidak bekerja di sini. Coba tebak di sini, tetapi mungkin perkiraan biaya gulungan itu tidak aktif - menggunakan TEMPDB - Anda mungkin memiliki TEMPDB yang berkinerja buruk.
* Berhati-hatilah dengan petunjuk bergabung , karena mereka memaksa urutan akses tabel rencana agar sesuai dengan urutan tertulis dari tabel dalam kueri (sama seperti jika
OPTION (FORCE ORDER)
telah ditentukan). Dari tautan dokumentasi:Ini mungkin tidak menghasilkan efek yang tidak diinginkan dalam contoh, tetapi secara umum, mungkin sangat baik.
FORCE ORDER
(tersirat atau eksplisit) adalah petunjuk yang sangat kuat yang melampaui penegakan ketertiban; itu mencegah berbagai teknik pengoptimal diterapkan, termasuk agregasi parsial dan pemesanan ulang.Sebuah
OPTION (HASH JOIN)
permintaan petunjuk mungkin kurang mengganggu dalam kasus yang cocok, karena ini tidak berartiFORCE ORDER
. Namun, itu berlaku untuk semua gabungan dalam kueri. Solusi lain tersedia.sumber
Karena Anda mendapatkan paket yang benar
ORDER BY
, mungkin Anda bisa memutarTOP
operator sendiri ?Dalam pikiran saya, rencana kueri untuk di
ROW_NUMBER()
atas harus sama dengan jika Anda memilikiORDER BY
. Rencana kueri sekarang harus memiliki Segmen, Proyek Urutan dan akhirnya operator Filter, sisanya harus seperti rencana bagus Anda.sumber
Sunting: +1 berfungsi dalam situasi ini karena ternyata itu
FILE_NUMBER
adalah versi string nol-empuk bilangan bulat. Solusi yang lebih baik di sini untuk string adalah menambahkan''
(string kosong), karena menambahkan nilai dapat mempengaruhi urutan, atau untuk angka untuk menambahkan sesuatu yang konstan tetapi berisi fungsi non-deterministik, sepertisign(rand()+1)
. Gagasan 'melanggar semacam itu' masih berlaku di sini, hanya saja metode saya tidak ideal.+1
Tidak, maksudku aku tidak setuju dengan apa pun, maksudku itu sebagai solusi. Jika Anda mengubah kueri untuk
ORDER BY cj.FILE_NUMBER + 1
makaTOP 1
akan berperilaku berbeda.Anda lihat, dengan sasaran baris kecil di tempat untuk permintaan yang dipesan, sistem akan mencoba untuk mengkonsumsi data dalam rangka, untuk menghindari memiliki operator Urutkan. Ini juga akan menghindari membangun tabel hash, dengan memperkirakan bahwa mungkin tidak perlu melakukan terlalu banyak pekerjaan untuk menemukan baris pertama. Dalam kasus Anda, ini salah - dari ketebalan panah-panah itu, sepertinya ia harus mengkonsumsi banyak data untuk menemukan satu kecocokan.
Ketebalan panah-panah itu menunjukkan bahwa
DOCUMENT_QUEUE
tabel (DQ) Anda jauh lebih kecil dariCORRESPONDENCE_JOURNAL
tabel (CJ) Anda. Dan bahwa rencana terbaik sebenarnya adalah memeriksa melalui baris DQ sampai baris CJ ditemukan. Memang, itulah yang akan dilakukan oleh Pengoptimal Kueri (QO) jika tidak ada sialORDER BY
di sana, itu didukung oleh indeks penutup pada CJ.Jadi, jika Anda menjatuhkan
ORDER BY
sepenuhnya, saya berharap Anda akan mendapatkan rencana yang melibatkan Nested Loop, iterasi di atas baris di DQ, mencari ke CJ untuk memastikan baris ada. Dan denganTOP 1
, ini akan berhenti setelah satu baris ditarik.Tetapi jika Anda benar-benar membutuhkan baris pertama dalam
FILE_NUMBER
urutan, maka Anda dapat menipu sistem untuk mengabaikan indeks yang tampaknya (salah) sangat membantu, dengan melakukanORDER BY CJ.FILE_NUMBER+1
- yang kita tahu akan menjaga urutan yang sama seperti sebelumnya, tetapi yang penting QO tidak. QO akan fokus untuk menyelesaikan keseluruhan, sehingga operator Sort N Top dapat puas. Metode ini harus menghasilkan rencana yang berisi operator Compute Scalar untuk menghitung nilai pemesanan, dan operator Sort N Top untuk mendapatkan baris pertama. Tetapi di sebelah kanan ini, Anda harus melihat Nested Loop yang bagus, melakukan banyak hal di CJ. Dan kinerja yang lebih baik daripada berlari melalui tabel besar baris yang tidak cocok dengan apa pun di DQ.Pertandingan Hash tidak selalu buruk, tetapi jika rangkaian baris yang Anda kembali dari DQ jauh lebih kecil dari CJ (seperti yang saya perkirakan), maka Pertandingan Hash akan memindai lebih banyak CJ dari yang dibutuhkan.
Catatan: Saya menggunakan +1 sebagai ganti +0 karena pengoptimal kueri cenderung mengenali bahwa +0 tidak mengubah apa pun. Tentu saja, hal yang sama mungkin berlaku untuk +1, jika tidak sekarang, maka di beberapa titik di masa mendatang.
sumber
Menambahkan
OPTION (QUERYTRACEON 4138)
mematikan efek tujuan baris untuk kueri itu saja, tanpa terlalu menentukan tentang rencana akhir, dan mungkin akan menjadi cara paling sederhana / paling langsung.Jika menambahkan petunjuk ini memberi Anda kesalahan izin (diperlukan untuk
DBCC TRACEON
), Anda bisa menerapkannya menggunakan panduan paket:Menggunakan
QUERYTRACEON
panduan dalam rencana oleh spaghettidba... atau cukup gunakan prosedur tersimpan:
Izin Apa yang
QUERYTRACEON
Dibutuhkan? oleh Kendra Littlesumber
Versi SQL Server yang lebih baru menawarkan opsi yang berbeda (dan bisa dibilang lebih baik) untuk berurusan dengan kueri yang mendapatkan kinerja suboptimal ketika pengoptimal mampu menerapkan pengoptimalan sasaran baris. SQL Server 2016 SP1 memperkenalkan
DISABLE_OPTIMIZER_ROWGOAL USE HINT
yang memiliki efek yang sama dengan jejak flag 4138. Jika Anda tidak pada versi itu, Anda juga dapat mempertimbangkan menggunakanOPTIMIZE FOR
petunjuk kueri untuk mendapatkan paket permintaan yang dirancang untuk mengembalikan semua baris, bukan hanya 1. Kueri di bawah ini akan mengembalikan hasil yang sama dengan yang ada di pertanyaan tetapi tidak akan dibuat dengan tujuan hanya mendapatkan 1 baris.sumber
Karena Anda sedang melakukan
TOP(1)
, saya sarankan untuk membuatORDER BY
deterministik sebagai permulaan. Paling tidak ini akan memastikan hasil yang dapat diprediksi secara fungsional (selalu berguna untuk pengujian regresi). Sepertinya Anda perlu menambahkanDC.D_ID
danCJ.CORRESPONDENCE_ID
untuk itu.Ketika melihat rencana kueri, kadang-kadang saya menemukan petunjuk untuk menyederhanakan kueri: Mungkin pilih semua baris dc yang relevan ke tabel temp di muka, untuk menghilangkan masalah dengan estimasi kardinalitas pada
QUEUE_DATE
danPRINT_LOCATION
. Ini harus cepat mengingat jumlah baris yang rendah. Anda kemudian dapat menambahkan indeks ke tabel temp ini jika perlu tanpa mengubah tabel permanen.sumber