Saya memiliki pandangan yang sangat penting, sangat lambat yang mencakup beberapa kondisi yang sangat buruk seperti ini di mana klausa. Saya juga sadar bahwa sambungannya kasar dan lambatvarchar(13)
bukan bidang identitas bilangan bulat, tetapi ingin meningkatkan kueri sederhana di bawah ini yang menggunakan tampilan ini:
CREATE VIEW [dbo].[vwReallySlowView] AS
AS
SELECT
I.booking_no_v32 AS bkno,
I.trans_type_v41 AS trantype,
B.Assigned_to_v61 AS Assignbk,
B.order_date AS dateo, B.HourBooked AS HBooked,
B.MinBooked AS MBooked, B.SecBooked AS SBooked,
I.prep_on AS Pon, I.From_locn AS Flocn,
I.Trans_to_locn AS TTlocn,
(CASE I.prep_on WHEN 'Y' THEN I.PDate ELSE I.FirstDate END) AS PrDate, I.PTimeH AS PrTimeH, I.PTimeM AS PrTimeM,
(CASE WHEN I.RetnDate < I.FirstDate THEN I.FirstDate ELSE I.RetnDate END) AS RDatev, I.bit_field_v41 AS bitField, I.FirstDate AS FDatev, I.BookDate AS DBooked,
I.TimeBookedH AS TBookH, I.TimeBookedM AS TBookM, I.TimeBookedS AS TBookS, I.del_time_hour AS dth, I.del_time_min AS dtm, I.return_to_locn AS rtlocn,
I.return_time_hour AS rth, I.return_time_min AS rtm, (CASE WHEN I.Trans_type_v41 IN (6, 7) AND (I.Trans_qty < I.QtyCheckedOut)
THEN 0 WHEN I.Trans_type_v41 IN (6, 7) AND (I.Trans_qty >= I.QtyCheckedOut) THEN I.Trans_Qty - I.QtyCheckedOut ELSE I.trans_qty END) AS trqty,
(CASE WHEN I.Trans_type_v41 IN (6, 7) THEN 0 ELSE I.QtyCheckedOut END) AS MyQtycheckedout, (CASE WHEN I.Trans_type_v41 IN (6, 7)
THEN 0 ELSE I.QtyReturned END) AS retqty, I.ID, B.BookingProgressStatus AS bkProg, I.product_code_v42, I.return_to_locn, I.AssignTo, I.AssignType,
I.QtyReserved, B.DeprepOn,
(CASE B.DeprepOn
WHEN 1 THEN B.DeprepDateTime
ELSE I.RetnDate
END) AS DeprepDateTime, I.InRack
FROM dbo.tblItemtran AS I
INNER JOIN -- booking_no = varchar(13)
dbo.tblbookings AS B ON B.booking_no = I.booking_no_v32 -- string inner-join
INNER JOIN -- product_code = varchar(13)
dbo.tblInvmas AS M ON I.product_code_v42 = M.product_code -- string inner-join
WHERE (I.trans_type_v41 NOT IN (2, 3, 7, 18, 19, 20, 21, 12, 13, 22)) AND (I.trans_type_v41 NOT IN (6, 7)) AND (I.bit_field_v41 & 4 = 0) OR
(I.trans_type_v41 NOT IN (6, 7)) AND (I.bit_field_v41 & 4 = 0) AND (B.BookingProgressStatus = 1) OR
(I.trans_type_v41 IN (6, 7)) AND (I.bit_field_v41 & 4 = 0) AND (I.QtyCheckedOut = 0) OR
(I.trans_type_v41 IN (6, 7)) AND (I.bit_field_v41 & 4 = 0) AND (I.QtyCheckedOut > 0) AND (I.trans_qty - (I.QtyCheckedOut - I.QtyReturned) > 0)
Tampilan ini biasanya digunakan seperti ini:
select * from vwReallySlowView
where product_code_v42 = 'LIGHTBULB100W' -- find "100 watt lightbulb" rows
Ketika saya menjalankannya saya mendapatkan item rencana eksekusi ini biaya 20 hingga 80% dari total biaya batch, dengan predikat CONVERT_IMPLICIT( .... &(4))
menunjukkan bahwa tampaknya sangat lambat dalam melakukan inibitwise boolean tests
seperti(I.ibitfield & 4 = 0)
.
Saya bukan ahli dalam MS SQL atau pada tipe DBA bekerja secara umum karena saya sebagian besar pengembang perangkat lunak non-SQL. Tetapi saya menduga bahwa kombinasi bitwise seperti itu adalah ide yang buruk, dan akan lebih baik jika memiliki bidang boolean yang terpisah.
Bisakah saya entah bagaimana meningkatkan indeks yang saya miliki ini, untuk menangani pandangan ini dengan lebih baik tanpa mengubah skema (yang sudah diproduksi di ribuan lokasi) atau haruskah saya mengubah tabel dasar yang memiliki beberapa nilai boolean yang dikemas menjadi bilangan bulat bit_field_v41
, untuk memperbaiki masalah ini ?
Inilah Indeks Clustered saya tblItemtran
yang sedang dipindai dalam rencana eksekusi ini:
-- goal: speed up select * from vwReallySlowView where productcode = 'X'
CREATE CLUSTERED INDEX [idxtblItemTranProductCodeAndTransType] ON [dbo].[tblItemtran]
(
[product_code_v42] ASC, -- varchar(13)
[trans_type_v41] ASC -- int
)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]
Berikut adalah rencana eksekusi, untuk salah satu produk lain yang menghasilkan biaya 27% pada CONVERT_IMPLICIT
predikat ini . pembaruan Perhatikan bahwa dalam kasus ini, simpul terburuk saya sekarang adalah "hash match" pada inner join
, yang biayanya 34% Saya percaya ini adalah biaya yang tidak dapat saya hindari kecuali saya bisa menghindari melakukan penggabungan pada string yang saat ini saya tidak bisa hindari. Singkirkan itu. KeduanyaINNER JOIN
operasi dalam tampilan di atas aktifvarchar(13)
bidang.
Memperbesar di sudut kanan bawah:
Seluruh paket eksekusi sebagai .sqlplan tersedia di skydrive. Gambar ini hanyalah gambaran visual. Klik di sini untuk melihat gambar dengan sendirinya.
Perbarui diposting seluruh rencana eksekusi. Saya sepertinya tidak dapat menemukan product_code
nilai yang buruk secara patologis, tetapi salah satu cara untuk melakukannya adalah dengan select count(*) from view
tidak melakukan satu produk. Tetapi produk yang hanya digunakan dalam 5% dari catatan dalam tabel yang mendasari atau kurang tampaknya menunjukkan biaya yang jauh lebih rendah dalam CONVERT_IMPLICIT
operasi. Jika saya akan memperbaiki SQL di sini, saya pikir saya akan mengambil WHERE
klausa kotor dalam tampilan, dan menghitung dan menyimpan hasil raksasa itu di mana klausa-kondisi sebagai bidang bit "IncludeMeInTheView", di tabel yang mendasarinya . Presto, masalah terpecahkan, bukan?
sumber
product_code
nilai yang saya gunakan untuk menghasilkan87%
kasus patologis dengan data saya. Gambar sekarang ditampilkan27%
. Sekali lagi, permintaan maaf untuk kebingungan karena suntingan saya.Jawaban:
Anda seharusnya tidak terlalu mengandalkan persentase biaya dalam rencana pelaksanaan. Ini selalu merupakan perkiraan biaya , bahkan dalam rencana pasca-eksekusi dengan angka 'aktual' untuk hal-hal seperti jumlah baris. Perkiraan biaya didasarkan pada model yang bekerja dengan sangat baik untuk tujuan yang dimaksudkan: memungkinkan pengoptimal untuk memilih antara berbagai rencana pelaksanaan kandidat untuk permintaan yang sama. Informasi biaya menarik, dan faktor yang perlu dipertimbangkan, tetapi seharusnya jarang menjadi metrik utama untuk penyetelan kueri. Menafsirkan informasi rencana eksekusi memerlukan pandangan yang lebih luas dari data yang disajikan.
Operator Pencarian Indeks ItemTran Clustered
Operator ini benar-benar dua operasi dalam satu. Pertama operasi pencarian indeks menemukan semua baris yang cocok dengan predikat
product_code_v42 = 'M10BOLT'
, maka setiap baris memiliki predikat residual yangbit_field_v41 & 4 = 0
diterapkan. Ada konversi implisit daribit_field_v41
tipe dasar (tinyint
atausmallint
) keinteger
.Konversi terjadi karena operator bitwise-AND (&) mengharuskan kedua operan memiliki tipe yang sama. Tipe implisit dari nilai konstan '4' adalah bilangan bulat dan aturan prioritas tipe data berarti
bit_field_v41
nilai bidang prioritas rendah dikonversi.Masalahnya (seperti itu) mudah diperbaiki dengan menulis predikat sebagai
bit_field_v41 & CONVERT(tinyint, 4) = 0
- yang berarti nilai konstan memiliki prioritas lebih rendah dan dikonversi (selama pelipatan konstan) daripada nilai kolom. Jikabit_field_v41
initinyint
tidak ada konversi terjadi sama sekali. Demikian juga,CONVERT(smallint, 4)
bisa digunakan kalaubit_field_v41
adasmallint
. Yang mengatakan, konversi bukan masalah kinerja dalam kasus ini, tetapi masih merupakan praktik yang baik untuk mencocokkan jenis dan menghindari konversi tersirat jika memungkinkan.Bagian utama dari perkiraan biaya pencarian ini adalah ke ukuran tabel dasar. Meskipun kunci indeks yang dikelompokkan itu sendiri cukup sempit, ukuran setiap baris besar. Definisi untuk tabel tidak diberikan, tetapi hanya kolom yang digunakan dalam tampilan menambahkan hingga lebar baris yang signifikan. Karena indeks berkerumun mencakup semua kolom, jarak antara kunci indeks berkerumun adalah lebar baris , bukan lebar kunci indeks . Penggunaan sufiks versi pada beberapa kolom menunjukkan bahwa tabel sebenarnya memiliki lebih banyak kolom untuk versi sebelumnya.
Melihat kolom seek, predicate residual, dan output, kinerja operator ini dapat diperiksa secara terpisah dengan membangun kueri yang setara (
1 <> 2
ini adalah trik untuk mencegah parameterisasi otomatis, kontradiksi dihapus oleh pengoptimal dan tidak muncul dalam rencana permintaan):Kinerja kueri ini dengan cache data dingin sangat menarik, karena read-ahead akan dipengaruhi oleh fragmentasi tabel (indeks berkerumun). Kunci pengelompokan untuk tabel ini mengundang fragmentasi, sehingga penting untuk memelihara (mengatur ulang atau membangun kembali) indeks ini secara teratur, dan menggunakan yang sesuai
FILLFACTOR
untuk memberikan ruang bagi baris baru di antara jendela pemeliharaan indeks.Saya melakukan tes efek fragmentasi pada baca-depan menggunakan data sampel yang dihasilkan menggunakan SQL Data Generator . Menggunakan jumlah baris tabel yang sama seperti yang ditunjukkan dalam rencana kueri pertanyaan, indeks cluster yang sangat terfragmentasi menghasilkan
SELECT * FROM view
waktu 15 detik setelahDBCC DROPCLEANBUFFERS
. Tes yang sama dalam kondisi yang sama dengan indeks berkerumun yang baru dibangun kembali pada tabel ItemTrans diselesaikan dalam 3 detik.Jika data tabel biasanya seluruhnya dalam cache, masalah fragmentasi sangat kurang penting. Tetapi, bahkan dengan fragmentasi rendah, baris tabel lebar mungkin berarti jumlah bacaan logis dan fisik jauh lebih tinggi dari yang mungkin diharapkan. Anda juga dapat bereksperimen dengan menambahkan dan menghapus eksplisit
CONVERT
untuk memvalidasi harapan saya bahwa masalah konversi implisit tidak penting di sini, kecuali sebagai pelanggaran praktik terbaik.Lebih penting lagi adalah perkiraan jumlah baris yang meninggalkan operator pencarian. Perkiraan waktu optimasi adalah 165 baris, tetapi 4.226 diproduksi pada waktu eksekusi. Saya akan kembali ke titik ini nanti, tetapi alasan utama untuk perbedaan ini adalah bahwa selektivitas predikat residual (yang melibatkan bitwise-AND) sangat sulit untuk diprediksi oleh pengoptimal - pada kenyataannya ia perlu menebak.
Operator Penyaring
Saya menunjukkan predikat filter di sini sebagian besar untuk menggambarkan bagaimana kedua
NOT IN
daftar digabungkan, disederhanakan dan kemudian diperluas, dan juga untuk memberikan referensi untuk diskusi pertandingan hash berikut. Permintaan tes dari pencarian dapat diperluas untuk memasukkan efeknya dan menentukan efek dari operator Filter pada kinerja:Operator Hitung Skalar dalam paket mendefinisikan ekspresi berikut (perhitungan itu sendiri ditangguhkan hingga hasilnya diperlukan oleh operator yang lebih baru):
Operator Pencocokan Hash
Melakukan penggabungan pada tipe data karakter bukan alasan tingginya estimasi biaya operator ini. Tip alat SSMS hanya menampilkan entri Hash Keys Probe, tetapi detail penting ada di jendela Properti SSMS.
Operator Pencocokan Hash membangun tabel hash menggunakan nilai-nilai
booking_no_v32
kolom (Hash Keys Build) dari tabel ItemTran, dan kemudian mencari kecocokan menggunakanbooking_no
kolom (Hash Keys Probe) dari tabel Pemesanan. Tooltip SSMS juga biasanya menampilkan Probe Residual, tetapi teksnya terlalu panjang untuk tooltip dan dihilangkan begitu saja.Sisa Probe mirip dengan Sisa yang terlihat setelah indeks mencari sebelumnya; predikat residual dievaluasi pada semua baris yang cocok hash untuk menentukan apakah baris harus diteruskan ke operator induk. Menemukan kecocokan hash di tabel hash yang seimbang sangat cepat, tetapi menerapkan predikat residu yang kompleks untuk setiap baris yang cocok cukup lambat jika dibandingkan. Tooltip Pencocokan Hash di Plan Explorer menunjukkan detail, termasuk ekspresi Probe Residual:
Predikat residual rumit dan termasuk status kemajuan pemesanan sekarang kolom tersedia dari tabel pemesanan. Tip alat juga menunjukkan perbedaan yang sama antara taksiran dan jumlah baris aktual yang terlihat sebelumnya dalam pencarian indeks. Mungkin aneh bahwa sebagian besar penyaringan dilakukan dua kali, tetapi ini hanya optimis yang optimis. Itu tidak mengharapkan bagian-bagian dari filter yang dapat menekan rencana dari residu probe untuk menghilangkan baris (perkiraan jumlah baris adalah sama sebelum dan setelah filter) tetapi pengoptimal tahu itu mungkin salah tentang hal itu. Peluang untuk memfilter baris lebih awal (mengurangi biaya hash join) sepadan dengan biaya yang kecil dari filter tambahan. Keseluruhan filter tidak dapat ditekan karena mencakup tes pada kolom dari tabel pemesanan, tetapi sebagian besar bisa.
Penghitungan baris meremehkan adalah masalah bagi operator Pencocokan Hash karena jumlah memori yang dicadangkan untuk tabel hash didasarkan pada perkiraan jumlah baris. Di mana memori terlalu kecil untuk ukuran tabel hash yang diperlukan pada waktu berjalan (karena jumlah baris yang lebih besar), tabel hash secara rekursi tumpah ke penyimpanan tempdb fisik , seringkali mengakibatkan kinerja yang sangat buruk. Dalam kasus terburuk, mesin eksekusi berhenti menumpahkan ember dan resor hash secara rekursif ke a sangat lambatalgoritma bailout. Tumpahan hash (rekursif atau bailout) adalah penyebab paling mungkin dari masalah kinerja yang diuraikan dalam pertanyaan (bukan kolom tipe karakter bergabung atau konversi tersirat). Penyebab utama adalah server menyimpan terlalu sedikit memori untuk kueri berdasarkan estimasi jumlah baris (kardinalitas) yang salah.
Sayangnya, sebelum SQL Server 2012, tidak ada indikasi dalam rencana eksekusi bahwa operasi hashing melebihi alokasi memorinya (yang tidak dapat tumbuh secara dinamis setelah dipesan sebelum eksekusi dimulai, bahkan jika server memiliki banyak memori bebas) dan harus tumpah ke tempdb. Dimungkinkan untuk memantau Kelas Acara Peringatan Hash menggunakan Profiler, tetapi mungkin sulit untuk menghubungkan peringatan dengan permintaan tertentu.
Memperbaiki masalah
Ketiga masalah tersebut adalah fragmentasi, residu probe kompleks pada operator hash match, dan estimasi kardinalitas yang salah dihasilkan dari tebakan pada pencarian indeks.
Solusi yang disarankan
Periksa fragmentasi dan perbaiki jika perlu, penjadwalan pemeliharaan untuk memastikan indeks tetap terorganisir. Cara biasa untuk mengoreksi perkiraan kardinalitas adalah dengan memberikan statistik. Dalam hal ini, pengoptimal membutuhkan statistik untuk kombinasi (
product_code_v42
,bitfield_v41 & 4 = 0
). Kami tidak dapat membuat statistik pada ekspresi secara langsung, jadi pertama-tama kami harus membuat kolom yang dikomputasi untuk ekspresi bidang bit, dan kemudian membuat statistik multi-kolom manual:Definisi teks kolom yang dihitung harus sesuai dengan teks dalam definisi tampilan cukup tepat untuk statistik yang akan digunakan, jadi mengoreksi tampilan untuk menghilangkan konversi implisit harus dilakukan pada saat yang sama, dan hati-hati untuk memastikan kecocokan tekstual.
Statistik multi-kolom seharusnya menghasilkan perkiraan yang jauh lebih baik, sangat mengurangi kemungkinan bahwa operator hash match akan menggunakan penumpahan rekursif atau algoritma bailout. Menambahkan kolom yang dikomputasi (yang merupakan operasi metadata saja, dan tidak memakan ruang dalam tabel karena tidak ditandai
PERSISTED
) dan statistik multi-kolom adalah tebakan terbaik saya pada solusi pertama.Saat memecahkan masalah kinerja permintaan, penting untuk mengukur hal-hal seperti waktu yang berlalu, penggunaan CPU, pembacaan logis, pembacaan fisik, jenis dan durasi tunggu ... dan seterusnya. Ini juga dapat berguna untuk menjalankan bagian dari kueri secara terpisah untuk memvalidasi dugaan penyebab seperti yang ditunjukkan di atas.
Di beberapa lingkungan, di mana tampilan data yang up-to-the-second tidak penting, dapat berguna untuk menjalankan proses latar belakang yang mematerialisasikan seluruh tampilan ke dalam tabel snapshot sesering mungkin. Tabel ini hanya tabel dasar normal dan dapat diindeks untuk permintaan baca tanpa khawatir tentang dampak kinerja pembaruan.
Lihat pengindeksan
Jangan tergoda untuk mengindeks tampilan asli secara langsung. Kinerja baca akan luar biasa cepat (pencarian tunggal pada indeks tampilan) tetapi (dalam hal ini) semua masalah kinerja dalam rencana kueri yang ada akan ditransfer ke kueri yang mengubah salah satu kolom tabel yang direferensikan dalam tampilan. Pertanyaan yang mengubah baris tabel dasar akan berdampak sangat buruk.
Solusi canggih dengan tampilan sebagian terindeks
Ada solusi tampilan indeks parsial untuk kueri khusus ini yang mengoreksi perkiraan kardinalitas dan menghilangkan sisa filter dan probe, tetapi didasarkan pada beberapa asumsi tentang data (kebanyakan dugaan saya di skema) dan memerlukan implementasi pakar, khususnya yang sesuai indeks untuk mendukung rencana pemeliharaan tampilan terindeks. Saya membagikan kode di bawah ini untuk minat, saya tidak menyarankan Anda menerapkannya tanpa analisis dan pengujian yang sangat hati-hati .
Tampilan yang ada diubah untuk menggunakan tampilan yang diindeks di atas:
Contoh kueri dan rencana eksekusi:
Dalam rencana baru, pencocokan hash tidak memiliki predikat residual , tidak ada filter kompleks , tidak ada predikat residual pada pencarian tampilan yang diindeks, dan perkiraan kardinalitas tepat.
Sebagai contoh bagaimana pengaruh sisipkan / perbarui / hapus rencana, ini adalah rencana penyisipan ke tabel ItemTrans:
Bagian yang disorot adalah baru dan diperlukan untuk pemeliharaan tampilan yang diindeks. Kumparan meja memutar ulang baris tabel dasar yang dimasukkan untuk pemeliharaan tampilan terindeks. Setiap baris digabungkan ke tabel pemesanan menggunakan pencarian indeks berkerumun, kemudian filter menerapkan kompleks
WHERE
predikat klausa untuk melihat apakah baris perlu ditambahkan ke tampilan. Jika demikian, penyisipan dilakukan ke indeks pengelompokan tampilan.Sama
SELECT * FROM view
Tes yang dilakukan sebelumnya diselesaikan dalam 150 ms dengan tampilan indeks di tempat.Hal terakhir: Saya perhatikan server 2008 R2 Anda masih di RTM. Itu tidak akan memperbaiki masalah kinerja Anda, tetapi Paket Layanan 2 untuk 2008 R2 telah tersedia sejak Juli 2012, dan ada banyak alasan bagus untuk tetap serahasia mungkin dengan paket layanan.
sumber