Saya memiliki tabel yang agak besar dengan salah satu kolom menjadi data XML dengan ukuran rata-rata entri XML ~ 15 kilobyte. Semua kolom lainnya adalah int reguler, bigint, GUID, dll. Untuk memiliki angka konkret, misalkan tabel memiliki sejuta baris dan berukuran ~ 15 GB.
Apa yang saya perhatikan adalah bahwa tabel ini sangat lambat untuk memilih data jika saya ingin memilih semua kolom. Kapan saya melakukannya
SELECT TOP 1000 * FROM TABLE
dibutuhkan sekitar 20-25 detik untuk membaca data dari disk - meskipun saya tidak memaksakan pemesanan pada hasilnya. Saya menjalankan query dengan cache dingin (yaitu setelah DBCC DROPCLEANBUFFERS
). Inilah hasil statistik IO:
Pindai hitungan 1, bacaan logis 364, bacaan fisik 24, bacalah bacaan 7191, lob logis bacalah 7924, lob fisik bacaan 1690, bacus bacalah bacaan 3968.
Itu mengambil ~ 15 MB data. Rencana pelaksanaan menunjukkan Pemindaian Indeks Berkelompok seperti yang saya harapkan.
Tidak ada IO yang terjadi pada disk selain pertanyaan saya; Saya juga telah memeriksa bahwa fragmentasi indeks berkerumun dekat dengan 0%. Ini adalah drive SATA tingkat konsumen, namun saya masih berpikir SQL Server akan dapat memindai tabel lebih cepat dari ~ 100-150 MB / menit.
Kehadiran bidang XML menyebabkan sebagian besar data tabel berada di halaman LOB_DATA (bahkan ~ 90% dari halaman tabel adalah LOB_DATA).
Saya kira pertanyaan saya adalah - apakah saya benar dalam berpikir bahwa halaman LOB_DATA dapat menyebabkan pemindaian lambat bukan hanya karena ukurannya, tetapi juga karena SQL Server tidak dapat memindai indeks yang dikelompokkan secara efektif ketika ada banyak halaman LOB_DATA di tabel?
Bahkan lebih luas lagi - apakah dianggap layak untuk memiliki struktur tabel / pola data seperti itu? Rekomendasi untuk menggunakan Filestream biasanya menyatakan ukuran bidang yang jauh lebih besar, jadi saya tidak benar-benar ingin menempuh rute itu. Saya belum benar-benar menemukan info bagus tentang skenario khusus ini.
Saya sudah memikirkan kompresi XML, tetapi perlu dilakukan pada klien atau dengan SQLCLR dan akan membutuhkan beberapa pekerjaan untuk diimplementasikan dalam sistem.
Saya mencoba kompresi, dan karena XML sangat redundan, saya dapat (dalam aplikasi ac #) mengkompresi XML dari 20KB menjadi ~ 2.5KB dan menyimpannya dalam kolom VARBINARY, mencegah penggunaan halaman data LOB. Ini mempercepat SELECT 20x kali dalam pengujian saya.
sumber
SELECT *
bukan masalah jika Anda membutuhkan data XML. Ini hanya masalah jika Anda tidak ingin data XML, dalam hal ini mengapa memperlambat permintaan untuk mendapatkan kembali data yang tidak Anda gunakan? Saya bertanya tentang pembaruan XML yang bertanya-tanya apakah fragmentasi pada halaman LOB tidak dilaporkan secara akurat. Itulah mengapa saya bertanya dalam jawaban saya bagaimana tepatnya Anda menentukan bahwa indeks yang dikelompokkan tidak terfragmentasi? Bisakah Anda memberikan perintah yang Anda jalankan? Dan sudahkah Anda melakukan REBUILD penuh pada Indeks Clustered? (lanjutan)Jawaban:
Hanya memiliki kolom XML di tabel tidak memiliki efek itu. Keberadaan data XML yang, dalam kondisi tertentu , menyebabkan sebagian data baris disimpan di luar baris, pada halaman LOB_DATA. Dan sementara satu (atau mungkin beberapa ;-) mungkin berpendapat bahwa ya,
XML
kolom menyiratkan bahwa memang akan ada data XML, itu tidak dijamin bahwa data XML perlu disimpan dari baris: kecuali baris cukup banyak sudah diisi di luar data XML apa pun, dokumen kecil (hingga 8000 byte) mungkin sesuai dan tidak pernah masuk ke halaman LOB_DATA.Pemindaian mengacu pada melihat semua baris. Tentu saja, ketika halaman data dibaca, semua data in-row dibaca, bahkan jika Anda memilih subset dari kolom. Perbedaannya dengan data LOB adalah bahwa jika Anda tidak memilih kolom itu, maka data offline tidak akan dibaca. Oleh karena itu tidak benar-benar adil untuk menarik kesimpulan tentang seberapa efisien SQL Server dapat memindai Indeks Clustered ini karena Anda tidak benar-benar menguji itu (atau Anda menguji setengahnya). Anda memilih semua kolom, yang termasuk kolom XML, dan seperti yang Anda sebutkan, di situlah sebagian besar data berada.
Jadi kita sudah tahu bahwa
SELECT TOP 1000 *
tes tersebut tidak hanya membaca serangkaian halaman data 8k, semuanya berturut-turut, tetapi melompat ke lokasi lain per setiap baris . Struktur pasti dari data LOB tersebut dapat bervariasi berdasarkan pada seberapa besar itu. Berdasarkan penelitian yang ditunjukkan di sini ( Berapa Ukuran Pointer LOB untuk (MAX) Jenis Seperti Varchar, Varbinary, Etc? ), Ada dua jenis alokasi LOB offline:Salah satu dari dua situasi ini terjadi setiap kali Anda mengambil data LOB yang lebih dari 8000 byte atau hanya tidak sesuai di baris. Saya memposting skrip pengujian pada PasteBin.com (skrip T-SQL untuk menguji alokasi LOB dan membaca ) yang menunjukkan 3 jenis alokasi LOB (berdasarkan ukuran data) serta efek masing-masing memiliki pada logis dan berbunyi secara fisik. Dalam kasus Anda, jika data XML benar-benar kurang dari 42.000 byte per baris, maka tidak satu pun (atau sangat sedikit) yang seharusnya berada dalam struktur TEXT_TREE paling tidak efisien.
Jika Anda ingin menguji seberapa cepat SQL Server dapat memindai Indeks Clustered itu, lakukan
SELECT TOP 1000
tetapi tentukan satu atau lebih kolom yang tidak termasuk kolom XML itu. Bagaimana hal itu memengaruhi hasil Anda? Seharusnya sedikit lebih cepat.Mengingat kami memiliki deskripsi yang tidak lengkap tentang struktur tabel aktual dan pola data, jawaban apa pun mungkin tidak optimal tergantung pada detail yang hilang tersebut. Dengan pemikiran itu, saya akan mengatakan bahwa tidak ada yang jelas tidak masuk akal tentang struktur tabel atau pola data Anda.
Itu membuat memilih semua kolom, atau bahkan hanya data XML (sekarang masuk
VARBINARY
) lebih cepat, tetapi sebenarnya menyakiti permintaan yang tidak memilih data "XML". Dengan asumsi Anda memiliki sekitar 50 byte di kolom lain dan memilikiFILLFACTOR
100, maka:Tanpa Kompresi: 15k
XML
data harus memerlukan 2 halaman LOB_DATA, yang kemudian membutuhkan 2 petunjuk untuk Inline Root. Pointer pertama adalah 24 byte dan yang kedua adalah 12, untuk total 36 byte yang disimpan dalam baris untuk data XML. Ukuran baris total adalah 86 byte, dan Anda dapat memuat sekitar 93 baris tersebut ke halaman data 8060 byte. Karenanya, 1 juta baris membutuhkan 10.753 halaman data.Kompresi Kustom: 2.5k
VARBINARY
data akan sesuai di baris. Ukuran baris total adalah 2610 (2,5 * 1024 = 2560) byte, dan Anda hanya bisa memasukkan 3 baris tersebut ke halaman data 8060 byte. Karenanya, 1 juta baris membutuhkan 333.334 halaman data.Ergo, menerapkan hasil kompresi khusus dalam peningkatan 30x halaman data untuk Indeks Clustered. Artinya, semua kueri yang menggunakan pemindaian Indeks Clustered sekarang memiliki sekitar 322.500 lebih banyak halaman data untuk dibaca. Silakan lihat bagian terperinci di bawah ini untuk konsekuensi tambahan dari melakukan jenis kompresi ini.
Saya akan memperingatkan untuk tidak melakukan refactoring apa pun berdasarkan kinerja
SELECT TOP 1000 *
. Itu sepertinya bukan permintaan yang bahkan akan dikeluarkan aplikasi, dan tidak boleh digunakan sebagai satu-satunya dasar untuk optimasi yang mungkin tidak perlu.Untuk info lebih rinci dan lebih banyak tes untuk mencoba, silakan lihat bagian di bawah ini.
Pertanyaan ini tidak dapat diberikan jawaban yang pasti, tetapi setidaknya kita dapat membuat beberapa kemajuan dan menyarankan penelitian tambahan untuk membantu menggerakkan kita lebih dekat untuk mencari tahu masalah yang sebenarnya (idealnya berdasarkan bukti).
Apa yang kita ketahui:
XML
kolom dan beberapa kolom lain dari jenis:INT
,BIGINT
,UNIQUEIDENTIFIER
, "dll"XML
Kolom "ukuran" adalah, rata-rata sekitar 15kDBCC DROPCLEANBUFFERS
, dibutuhkan 20 - 25 detik untuk menyelesaikan kueri berikut:SELECT TOP 1000 * FROM TABLE
Apa yang kami pikir kami tahu:
Kompresi XML mungkin membantu. Bagaimana tepatnya Anda melakukan kompresi di .NET? Melalui kelas GZipStream atau DeflateStream ? Ini bukan opsi tanpa biaya. Ini tentu akan memampatkan beberapa data dengan persentase besar, tetapi juga akan membutuhkan lebih banyak CPU karena Anda akan memerlukan proses tambahan untuk mengompres / mendekompresi data setiap kali. Paket ini juga akan sepenuhnya menghilangkan kemampuan Anda untuk:
.nodes
,.value
,.query
, dan.modify
fungsi XML.indeks data XML.
Harap diingat (karena Anda menyebutkan bahwa XML "sangat redundan") bahwa
XML
datatype sudah dioptimalkan karena ia menyimpan elemen dan nama atribut dalam kamus, menetapkan ID indeks integer untuk setiap item, dan kemudian menggunakan ID integer itu di seluruh dokumen (karenanya tidak mengulangi nama lengkap per setiap penggunaan, juga tidak mengulanginya lagi sebagai tag penutup untuk elemen). Data aktual juga memiliki ruang putih asing yang dihapus. Inilah sebabnya mengapa dokumen XML yang diekstraksi tidak mempertahankan struktur aslinya dan mengapa elemen kosong mengekstraksi<element />
bahkan jika mereka masuk sebagai<element></element>
. Jadi setiap keuntungan dari mengompresi melalui GZip (atau apa pun) hanya akan ditemukan dengan mengompresi nilai elemen dan / atau atribut, yang merupakan area permukaan yang jauh lebih kecil yang dapat ditingkatkan daripada yang diperkirakan kebanyakan orang, dan kemungkinan besar tidak sebanding dengan hilangnya kemampuan seperti disebutkan secara langsung di atas.Harap juga diingat bahwa mengompresi data XML dan menyimpan
VARBINARY(MAX)
hasilnya tidak akan menghilangkan akses LOB, itu hanya akan mengurangi itu. Bergantung pada ukuran data lainnya pada baris, nilai yang dikompresi mungkin cocok dalam baris, atau mungkin masih membutuhkan halaman LOB.Informasi itu, walaupun bermanfaat, tidak cukup. Ada banyak faktor yang memengaruhi kinerja kueri, jadi kita perlu gambaran yang lebih rinci tentang apa yang terjadi.
Apa yang tidak kita ketahui, tetapi perlu:
SELECT *
materi? Apakah ini pola yang Anda gunakan dalam kode. Jika demikian, mengapa?SELECT TOP 1000 XmlColumn FROM TABLE;
?Berapa banyak dari 20 - 25 detik yang diperlukan untuk mengembalikan 1000 baris ini terkait dengan faktor-faktor jaringan (mendapatkan data melalui kabel), dan berapa banyak yang terkait dengan faktor-faktor klien (rendering sekitar 15 MB ditambah sisa non- Data XML ke dalam grid di SSMS, atau mungkin disimpan ke disk)?
Anjak kedua aspek operasi ini kadang-kadang dapat dilakukan dengan tidak mengembalikan data. Sekarang, orang mungkin berpikir untuk memilih ke dalam Tabel Sementara atau Tabel Variabel, tetapi ini hanya akan memperkenalkan beberapa variabel baru (yaitu disk I / O untuk
tempdb
, tulis Transaction Log, kemungkinan tumbuhnya otomatis data tempdb dan / atau file log, perlu ruang di Buffer Pool, dll). Semua faktor baru itu sebenarnya dapat meningkatkan waktu kueri. Sebagai gantinya, saya biasanya menyimpan kolom ke variabel (dari tipe data yang sesuai; tidakSQL_VARIANT
) yang ditimpa dengan setiap baris baru (yaituSELECT @Column1 = tab.Column1,...
).NAMUN , seperti yang ditunjukkan oleh @PaulWhite dalam DBA ini. Tanya Jawab T&J, Logika berbunyi berbeda ketika mengakses data LOB yang sama , dengan penelitian tambahan yang saya posting di PasteBin ( skrip T-SQL untuk menguji berbagai skenario untuk pembacaan LOB ) , LOB tidak diakses secara konsisten antara
SELECT
,SELECT INTO
,SELECT @XmlVariable = XmlColumn
,SELECT @XmlVariable = XmlColumn.query(N'/')
, danSELECT @NVarCharVariable = CONVERT(NVARCHAR(MAX), XmlColumn)
. Jadi pilihan kita sedikit lebih terbatas di sini, tetapi di sini adalah apa yang bisa dilakukan:Atau, Anda bisa mengeksekusi query melalui sqlcmd.exe dan mengarahkan output untuk pergi ke mana-mana melalui:
-o NUL:
.Berapa ukuran data aktual untuk
XML
kolom yang dikembalikan ? Ukuran rata-rata kolom di seluruh tabel tidak terlalu menjadi masalah jika baris "TOP 1000" berisi sebagian besar dari totalXML
data secara tidak proporsional . Jika Anda ingin tahu tentang baris TOP 1000, maka lihatlah baris itu. Silakan jalankan yang berikut ini:CREATE TABLE
, termasuk semua indeks.Apa hasil pasti dari kueri berikut:
MEMPERBARUI
Terpikir oleh saya bahwa saya harus mencoba mereproduksi skenario ini untuk melihat apakah saya mengalami perilaku yang sama. Jadi, saya membuat tabel dengan beberapa kolom (mirip dengan deskripsi yang tidak jelas dalam Pertanyaan), dan kemudian mengisinya dengan 1 juta baris, dan kolom XML memiliki sekitar 15k data per baris (lihat kode di bawah).
Apa yang saya temukan adalah melakukan
SELECT TOP 1000 * FROM TABLE
selesai dalam 8 detik pertama kali, dan 2 - 4 detik setiap kali sesudahnya (ya, mengeksekusiDBCC DROPCLEANBUFFERS
sebelum menjalankan setiapSELECT *
query). Dan laptop saya yang berusia beberapa tahun tidak cepat: Edisi Pengembang SQL Server 2012 SP2, 64 bit, RAM 6 GB, dual 2,5 Ghz Core i5, dan drive SATA 5400 RPM. Saya juga menjalankan SSMS 2014, SQL Server Express 2014, Chrome, dan beberapa hal lainnya.Berdasarkan waktu respons sistem saya, saya akan mengulangi bahwa kami memerlukan lebih banyak info (yaitu spesifik tentang tabel dan data, hasil tes yang disarankan, dll) untuk membantu mempersempit penyebab waktu respons 20 - 25 detik yang Anda lihat.
Dan, karena kami ingin memperhitungkan waktu yang diperlukan untuk membaca halaman non-LOB, saya menjalankan kueri berikut untuk memilih semua kecuali kolom XML (salah satu tes yang saya sarankan di atas). Ini kembali dalam 1,5 detik cukup konsisten.
Kesimpulan (untuk saat ini)
Berdasarkan upaya saya untuk membuat ulang skenario Anda, saya tidak berpikir kita dapat menunjuk ke drive SATA atau I / O non-sekuensial sebagai penyebab utama 20 - 25 detik, terutama karena kita masih tidak tahu seberapa cepat kueri kembali ketika tidak termasuk kolom XML. Dan saya tidak dapat mereproduksi sejumlah besar Logical Reads (non-LOB) yang Anda tampilkan, tetapi saya merasa bahwa saya perlu menambahkan lebih banyak data ke setiap baris dengan mengingatnya dan pernyataan:
Tabel saya memiliki 1 juta baris, masing-masing memiliki lebih dari
sys.dm_db_index_physical_stats
15rb data XML, dan menunjukkan bahwa ada 2 juta halaman LOB_DATA. 10% sisanya akan menjadi 222k IN_ROW halaman data, namun saya hanya memiliki 11.630 halaman. Jadi sekali lagi, kita memerlukan lebih banyak info mengenai skema tabel aktual dan data aktual.sumber
Ya, membaca data LOB yang tidak disimpan dalam baris mengarah ke IO acak, bukan IO berurutan. Metrik kinerja disk yang digunakan di sini untuk memahami mengapa ia cepat atau lambat adalah Random Read IOPS.
Data LOB disimpan dalam struktur pohon di mana halaman data dalam indeks berkerumun menunjuk ke halaman Data LOB dengan struktur akar LOB yang pada gilirannya menunjuk ke data LOB yang sebenarnya. Ketika melintasi node root dalam indeks cluster SQL Server hanya bisa mendapatkan data in-row dengan membaca berurutan. Untuk mendapatkan data LOB, SQL Server harus pergi ke tempat lain pada disk.
Saya kira jika Anda mengubah ke disk SSD, Anda tidak akan menderita sebanyak ini karena IOPS acak untuk SSD jauh lebih tinggi daripada disk berputar.
Ya bisa. Bergantung pada apa yang dilakukan tabel ini untuk Anda.
Biasanya masalah kinerja dengan XML dalam SQL Server terjadi ketika Anda ingin menggunakan T-SQL untuk permintaan ke XML dan bahkan lebih ketika Anda ingin menggunakan nilai-nilai dari XML dalam predikat di mana klausa atau bergabung. Jika itu masalahnya Anda bisa melihat-lihat promosi properti atau indeks XML selektif atau mendesain ulang struktur tabel Anda merobek XML ke tabel saja.
Saya melakukannya sekali dalam suatu produk sedikit lebih dari 10 tahun yang lalu dan telah menyesalinya sejak itu. Saya sangat merindukan tidak bisa bekerja dengan data menggunakan T-SQL, jadi saya tidak akan merekomendasikan itu kepada siapa pun jika itu dapat dihindari.
sumber