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 yang bit_field_v41 & 4 = 0
diterapkan. Ada konversi implisit dari bit_field_v41
tipe dasar ( tinyint
atau smallint
) 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. Jika bit_field_v41
ini tinyint
tidak ada konversi terjadi sama sekali. Demikian juga, CONVERT(smallint, 4)
bisa digunakan kalau bit_field_v41
ada smallint
. 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):
SELECT
it.booking_no_v32,
it.QtyCheckedOut,
it.QtyReturned,
it.Trans_qty,
it.trans_type_v41
FROM dbo.tblItemTran AS it
WHERE
1 <> 2
AND it.product_code_v42 = 'M10BOLT'
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0;
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:
SELECT
it.booking_no_v32,
it.trans_type_v41,
it.Trans_qty,
it.QtyReturned,
it.QtyCheckedOut
FROM dbo.tblItemTran AS it
WHERE
it.product_code_v42 = 'M10BOLT'
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
AND
(
(
it.trans_type_v41 NOT IN (2, 3, 6, 7, 18, 19, 20, 21, 12, 13, 22)
AND it.trans_type_v41 NOT IN (6, 7)
)
OR
(
it.trans_type_v41 NOT IN (6, 7)
)
OR
(
it.trans_type_v41 IN (6, 7)
AND it.QtyCheckedOut = 0
)
OR
(
it.trans_type_v41 IN (6, 7)
AND it.QtyCheckedOut > 0
AND it.trans_qty - (it.QtyCheckedOut - it.QtyReturned) > 0
)
);
Operator Hitung Skalar dalam paket mendefinisikan ekspresi berikut (perhitungan itu sendiri ditangguhkan hingga hasilnya diperlukan oleh operator yang lebih baru):
[Expr1016] = (trans_qty - (QtyCheckedOut - QtyReturned))
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 menggunakan booking_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:
ALTER TABLE dbo.tblItemTran
ADD Bit3 AS bit_field_v41 & CONVERT(tinyint, 4);
CREATE STATISTICS [stats dbo.ItemTran (product_code_v42, Bit3)]
ON dbo.tblItemTran (product_code_v42, Bit3);
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 .
-- Indexed view to optimize the main view
CREATE VIEW dbo.V1
WITH SCHEMABINDING
AS
SELECT
it.ID,
it.product_code_v42,
it.trans_type_v41,
it.booking_no_v32,
it.Trans_qty,
it.QtyReturned,
it.QtyCheckedOut,
it.QtyReserved,
it.bit_field_v41,
it.prep_on,
it.From_locn,
it.Trans_to_locn,
it.PDate,
it.FirstDate,
it.PTimeH,
it.PTimeM,
it.RetnDate,
it.BookDate,
it.TimeBookedH,
it.TimeBookedM,
it.TimeBookedS,
it.del_time_hour,
it.del_time_min,
it.return_to_locn,
it.return_time_hour,
it.return_time_min,
it.AssignTo,
it.AssignType,
it.InRack
FROM dbo.tblItemTran AS it
JOIN dbo.tblBookings AS tb ON
tb.booking_no = it.booking_no_v32
WHERE
(
it.trans_type_v41 NOT IN (2, 3, 7, 18, 19, 20, 21, 12, 13, 22)
AND it.trans_type_v41 NOT IN (6, 7)
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
)
OR
(
it.trans_type_v41 NOT IN (6, 7)
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
AND tb.BookingProgressStatus = 1
)
OR
(
it.trans_type_v41 IN (6, 7)
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
AND it.QtyCheckedOut = 0
)
OR
(
it.trans_type_v41 IN (6, 7)
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
AND it.QtyCheckedOut > 0
AND it.trans_qty - (it.QtyCheckedOut - it.QtyReturned) > 0
);
GO
CREATE UNIQUE CLUSTERED INDEX cuq ON dbo.V1 (product_code_v42, ID);
GO
Tampilan yang ada diubah untuk menggunakan tampilan yang diindeks di atas:
CREATE VIEW [dbo].[vwReallySlowView2]
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.V1 AS I WITH (NOEXPAND)
JOIN dbo.tblbookings AS B ON
B.booking_no = I.booking_no_v32
JOIN dbo.tblInvmas AS M ON
I.product_code_v42 = M.product_code;
Contoh kueri dan rencana eksekusi:
SELECT
vrsv.*
FROM dbo.vwReallySlowView2 AS vrsv
WHERE vrsv.product_code_v42 = 'M10BOLT';
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 kompleksWHERE
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.