Saya memiliki tampilan besar yang saya gunakan dari dalam suatu aplikasi. Saya pikir saya sudah mempersempit masalah kinerja saya, tetapi saya tidak yakin bagaimana cara memperbaikinya. Versi tampilan yang disederhanakan terlihat seperti ini:
SELECT ISNULL(SEId + '-' + PEId, '0-0') AS Id,
*,
DATEADD(minute, Duration, EventTime) AS EventEndTime
FROM (
SELECT se.SEId, pe.PEId,
COALESCE(pe.StaffName, se.StaffName) AS StaffName, -- << Problem!
COALESCE(pe.EventTime, se.EventTime) AS EventTime,
COALESCE(pe.EventType, se.EventType) AS EventType,
COALESCE(pe.Duration, se.Duration) AS Duration,
COALESCE(pe.Data, se.Data) AS Data,
COALESCE(pe.Field, se.Field) AS Field,
pe.ThisThing, se.OtherThing
FROM PE pe FULL OUTER JOIN SE se
ON pe.StaffName = se.StaffName
AND pe.Duration = se.Duration
AND pe.EventTime = se.EventTime
WHERE NOT(pe.ThisThing = 1 AND se.OtherThing = 0)
) Z
Itu mungkin tidak membenarkan seluruh alasan untuk struktur kueri, tetapi mungkin memberi Anda ide - pandangan ini bergabung dengan dua tabel yang dirancang sangat buruk yang saya tidak punya kontrol atas dan mencoba untuk mensintesiskan beberapa informasi dari itu.
Jadi, karena ini adalah tampilan yang digunakan dari aplikasi, ketika mencoba mengoptimalkan saya membungkusnya dalam SELECT lain, seperti ini:
SELECT * FROM (
-- … above code …
) Q
WHERE StaffName = 'SMITH, JOHN Q'
karena aplikasi sedang mencari anggota staf tertentu dalam hasilnya.
Masalahnya tampaknya adalah COALESCE(pe.StaffName, se.StaffName) AS StaffName
bagian, dan saya memilih dari tampilan StaffName
. Jika saya mengubahnya ke pe.StaffName AS StaffName
atau se.StaffName AS StaffName
, masalah kinerja hilang (tetapi lihat pembaruan 2 di bawah) . Tetapi itu tidak akan berhasil karena satu sisi atau sisi yang lain FULL OUTER JOIN
bisa hilang, sehingga satu atau bidang lainnya mungkin NULL.
Bisakah saya refactor ini mengganti COALESCE(…)
dengan sesuatu yang lain, yang akan ditulis ulang ke dalam subquery?
Catatan lain:
- Saya sudah menambahkan beberapa indeks untuk memperbaiki masalah kinerja dengan sisa kueri - tanpa
COALESCE
itu sangat cepat. - Agak mengherankan saya, melihat rencana eksekusi tidak menaikkan bendera, bahkan ketika subquery dan
WHERE
pernyataan pembungkus disertakan. Total biaya subkueri dalam penganalisa adalah0.0065736
. Hmph. Diperlukan empat detik untuk mengeksekusi. - Mengubah aplikasi ke permintaan secara berbeda
(mis. Kembalimungkin berhasil, tetapi sebagai upaya terakhir - saya benar-benar berharap saya dapat mengoptimalkan tampilan tanpa harus resor untuk menyentuh aplikasi.pe.StaffName AS PEStaffName, se.StaffName AS SEStaffName
dan melakukanWHERE PEStaffName = 'X' OR SEStaffName = 'X'
) - Prosedur tersimpan mungkin akan lebih masuk akal untuk ini, tetapi aplikasi ini dibuat dengan Entity Framework, dan saya tidak tahu bagaimana membuatnya bagus dengan SP yang mengembalikan tipe tabel (topik lain seluruhnya).
Indeks
Indeks yang saya tambahkan sejauh ini terlihat seperti ini:
CREATE NONCLUSTERED INDEX [IX_PE_EventTime]
ON [dbo].[PE] ([EventTime])
INCLUDE ([StaffName],[Duration],[EventType],[Data],[Field],[ThisThing])
CREATE NONCLUSTERED INDEX [IX_SE_EventTime]
ON [dbo].[SE] ([EventTime])
INCLUDE ([StaffName],[Duration],[EventType],[Data],[Field],[OtherThing])
Memperbarui
Hmm ... Saya mencoba mensimulasikan perubahan yang terjadi di atas, dan itu tidak membantu. Yaitu, sebelum di ) Z
atas, saya menambahkan AND (pe.StaffName = 'SMITH, JOHN Q' OR se.StaffName = 'SMITH, JOHN Q')
, tetapi kinerjanya sama. Sekarang saya benar-benar tidak tahu harus mulai dari mana.
Perbarui 2
Komentar @ypercube tentang perlunya bergabung penuh membuat saya menyadari bahwa permintaan saya yang disintesis meninggalkan komponen yang mungkin penting. Sementara, ya, saya perlu bergabung penuh, tes yang saya lakukan di atas dengan menjatuhkan COALESCE
dan menguji hanya satu sisi bergabung untuk nilai non-nol akan membuat sisi lain dari bergabung penuh tidak relevan , dan pengoptimal mungkin menggunakan ini fakta untuk mempercepat permintaan. Juga, saya telah memperbarui contoh untuk menunjukkan bahwa StaffName
sebenarnya adalah salah satu kunci bergabung - yang mungkin memiliki pengaruh signifikan pada pertanyaan. Saya juga sekarang condong ke sarannya bahwa memecah ini menjadi serikat tiga arah, bukan bergabung penuh mungkin jawabannya, dan akan menyederhanakan banyak hal yang COALESCE
saya lakukan. Cobalah sekarang.
sumber
KeyField
, kedua indeksINCLUDE
tersebutStaffName
lapangan dan beberapa bidang lainnya. Saya dapat memposting definisi indeks dalam pertanyaan. Saya sedang mengerjakan ini pada server uji sehingga saya dapat menambahkan indeks yang menurut Anda mungkin berguna untuk dicoba!WHERE pe.ThisThing = 1 AND se.OtherThing = 0
kondisi yang membatalkanFULL OUTER
gabung dan membuat kueri setara dengan gabung dalam. Apakah Anda yakin Anda perlu bergabung LENGKAP?INNER JOIN
,LEFT JOIN
denganWHERE IS NULL
cek, KANAN BERGABUNG dengan IS NULL) dan kemudianUNION ALL
tiga bagian. Dengan cara ini tidak perlu menggunakanCOALESCE()
dan mungkin (mungkin saja) membantu pengoptimal untuk mengetahui penulisan ulang.Jawaban:
Ini agak panjang tetapi karena OP mengatakan itu bekerja, saya menambahkannya sebagai jawaban (jangan ragu untuk memperbaikinya jika Anda menemukan sesuatu yang salah).
Cobalah untuk memecah kueri internal menjadi tiga bagian (
INNER JOIN
,LEFT JOIN
denganWHERE IS NULL
cek,RIGHT JOIN
denganIS NULL
cek) dan kemudianUNION ALL
tiga bagian. Ini memiliki keuntungan sebagai berikut:Pengoptimal memiliki lebih sedikit opsi transformasi yang tersedia untuk
FULL
bergabung daripada untuk (lebih umum)INNER
danLEFT
bergabung.The
Z
table berasal dapat dihapus (Anda dapat melakukannya pula) dari tampilan definisi.The
NOT(pe.ThisThing = 1 AND se.OtherThing = 0)
akan dibutuhkan hanya padaINNER
bagian bergabung.Perbaikan kecil, penggunaannya
COALESCE()
akan minimal jika ada sama sekali (saya berasumsi bahwase.SEId
danpe.PEId
tidak dapat dibatalkan. Jika lebih banyak kolom tidak dapat dibatalkan, Anda akan dapat menghapus lebih banyakCOALESCE()
panggilan.)Lebih penting, pengoptimal dapat menekan segala kondisi di kueri Anda yang melibatkan kolom-kolom ini (sekarang
COALESCE()
tidak menghalangi dorongan)Semua hal di atas akan memberikan lebih banyak opsi kepada pengoptimal untuk mengubah / menulis ulang kueri apa pun yang menggunakan tampilan sehingga dapat menemukan rencana eksekusi yang dapat digunakan indeks pada tabel yang mendasari.
Secara keseluruhan, tampilan dapat ditulis sebagai:
sumber
Intuisi saya adalah bahwa ini seharusnya tidak menjadi masalah karena pada saat
COALESCE(pe.StaffName, se.StaffName) AS StaffName
melakukan apa pun semua baris dari dua sumber harus sudah ditarik dan dicocokkan sehingga pemanggilan fungsi dalam memori sederhana dibandingkan-ke-null-dan -memilih. Jelas ini bukan masalahnya jadi mungkin sesuatu di salah satu sumber (jika mereka adalah tampilan atau tabel turunan inline) atau tabel dasar (yaitu kurangnya indeks) membuat perencana kueri berpikir perlu memindai kolom ini secara terpisah.Tanpa lebih detail dari kueri persis yang Anda jalankan, struktur pendukung, dan rencana kueri yang dihasilkan, apa pun yang kami sarankan adalah dugaan.
Untuk mencoba memaksa perbandingan dilakukan setelah semua yang lain, Anda bisa mencoba cukup memilih kedua nilai dalam tabel deribed (
pe.StaffName AS pe.StaffName, se.StaffName AS seStaffName
) kemudian melakukan pick dalam permintaan luar (COALESCE(peStaffName, seStaffName) AS StaffName
), atau Anda bahkan bisa mendorong data dari permintaan dalam ke tabel sementara kemudian melakukan query luar dengan memilih dari itu (tapi itu akan membutuhkan prosedur tersimpan, dan tergantung pada jumlah baris dump-to-tempdb ini bisa mahal dan karenanya bermasalah dalam dirinya sendiri).sumber
Z
saat ini kembali dengan baris ~ 1.5m. Apa yang saya ingin lakukan adalah menulis ulang predikat itu ke dalam queryZ
sehingga akan menggunakan indeks ... tapi sekarang saya juga bingung karena ketika saya secara manual meletakkan predikat di sana, masih tidak menggunakan indeks ... jadi sekarang Saya tidak yakin.