Kehadiran bidang XML menyebabkan sebagian besar data tabel berada di halaman LOB_DATA (bahkan ~ 90% dari halaman tabel adalah LOB_DATA).
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.
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 berkerumun secara efektif ketika ada banyak halaman LOB_DATA dalam tabel?
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:
- Inline Root - untuk data antara 8001 dan 40.000 (benar-benar 42.000) byte, ruang memungkinkan, akan ada 1 hingga 5 pointer (24 - 72 byte) DALAM ROW yang mengarah langsung ke halaman LOB.
- TEXT_TREE - untuk data lebih dari 42.000 byte, atau jika 1 hingga 5 pointer tidak dapat dimasukkan secara berturut-turut, maka hanya akan ada pointer 24 byte ke halaman awal dari daftar pointer ke halaman LOB (yaitu " text_tree "halaman).
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.
apakah masuk akal untuk memiliki struktur tabel / pola data seperti itu?
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.
Saya dapat (dalam aplikasi # ac) mengompresi XML dari 20KB ke ~ 2.5KB dan menyimpannya dalam kolom VARBINARY, mencegah penggunaan halaman data LOB. Ini mempercepat SELECT 20x kali dalam pengujian saya.
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 memiliki FILLFACTOR
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:
- Tabel memiliki sekitar 1 juta baris
- Ukuran meja sekitar 15 GB
- Tabel berisi satu
XML
kolom dan beberapa kolom lain dari jenis: INT
, BIGINT
, UNIQUEIDENTIFIER
, "dll"
XML
Kolom "ukuran" adalah, rata-rata sekitar 15k
- Setelah berjalan
DBCC DROPCLEANBUFFERS
, dibutuhkan 20 - 25 detik untuk menyelesaikan kueri berikut:SELECT TOP 1000 * FROM TABLE
- Indeks Clustered sedang dipindai
- Fragmentasi pada Indeks Berkelompok dekat dengan 0%
Apa yang kami pikir kami tahu:
- Tidak ada aktivitas disk lain di luar kueri ini. Apakah kamu yakin Bahkan jika tidak ada permintaan pengguna lain, apakah ada operasi latar belakang yang terjadi? Apakah ada proses eksternal ke SQL Server yang berjalan pada mesin yang sama yang bisa mengambil beberapa IO? Mungkin tidak ada, tetapi tidak jelas hanya berdasarkan info yang diberikan.
- 15 MB data XML sedang dikembalikan. Berdasarkan apa nomor ini? Perkiraan yang diperoleh dari 1000 baris kali rata-rata 15rb data XML per baris? Atau agregasi terprogram dari apa yang diterima untuk permintaan itu? Jika itu hanya perkiraan, saya tidak akan bergantung padanya karena distribusi data XML mungkin tidak bahkan dengan cara yang tersirat oleh rata-rata sederhana.
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:
- query data XML melalui
.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:
- Mengapa kinerja
SELECT *
materi? Apakah ini pola yang Anda gunakan dalam kode. Jika demikian, mengapa?
- Apa kinerja memilih hanya kolom XML? Apa statistik dan waktu jika Anda melakukan hal:
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; tidak SQL_VARIANT
) yang ditimpa dengan setiap baris baru (yaitu SELECT @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'/')
, dan SELECT @NVarCharVariable = CONVERT(NVARCHAR(MAX), XmlColumn)
. Jadi pilihan kita sedikit lebih terbatas di sini, tetapi di sini adalah apa yang bisa dilakukan:
- Singkirkan masalah jaringan dengan menjalankan kueri di server yang menjalankan SQL Server, baik dalam SSMS atau SQLCMD.EXE.
- Singkirkan masalah klien di SSMS dengan masuk ke Opsi Kueri -> Hasil -> Kisi dan centang opsi untuk "Buang hasil setelah eksekusi". Harap dicatat bahwa opsi ini akan mencegah SEMUA output, termasuk pesan, tetapi masih dapat berguna untuk mengesampingkan waktu yang dibutuhkan SSMS untuk mengalokasikan memori per setiap baris dan kemudian menggambarnya di grid.
Atau, Anda bisa mengeksekusi query melalui sqlcmd.exe dan mengarahkan output untuk pergi ke mana-mana melalui: -o NUL:
.
- Apakah ada Tipe Tunggu yang dikaitkan dengan permintaan ini? Jika ya, Jenis Tunggu apa itu?
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 total XML
data secara tidak proporsional . Jika Anda ingin tahu tentang baris TOP 1000, maka lihatlah baris itu. Silakan jalankan yang berikut ini:
SELECT TOP 1000 tab.*,
SUM(DATALENGTH(tab.XmlColumn)) / 1024.0 AS [TotalXmlKBytes],
AVG(DATALENGTH(tab.XmlColumn)) / 1024.0 AS [AverageXmlKBytes]
STDEV(DATALENGTH(tab.XmlColumn)) / 1024.0 AS [StandardDeviationForXmlKBytes]
FROM SchemaName.TableName tab;
- The tepat tabel skema. Harap berikan pernyataan lengkap
CREATE TABLE
, termasuk semua indeks.
- Paket pertanyaan? Apakah itu sesuatu yang dapat Anda posting? Info itu mungkin tidak akan mengubah apa pun, tetapi lebih baik untuk mengetahui bahwa itu tidak akan terjadi daripada menebak bahwa itu tidak akan dan salah ;-)
- Apakah ada fragmentasi fisik / eksternal pada file data? Meskipun ini mungkin bukan faktor besar di sini, karena Anda menggunakan "SATA tingkat konsumen" dan bukan SSD atau bahkan Super-Mahal SATA, efek dari sektor yang dipesan secara tidak optimal akan lebih terlihat, terutama karena jumlah sektor tersebut yang perlu dibaca meningkat.
Apa hasil pasti dari kueri berikut:
SELECT * FROM sys.dm_db_index_physical_stats(DB_ID(),
OBJECT_ID(N'dbo.SchemaName.TableName'), 1, 0, N'LIMITED');
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, mengeksekusi DBCC DROPCLEANBUFFERS
sebelum menjalankan setiap SELECT *
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.
SET ANSI_NULLS, NOCOUNT ON;
GO
IF (OBJECT_ID(N'dbo.XmlReadTest') IS NOT NULL)
BEGIN
PRINT N'Dropping table...';
DROP TABLE dbo.XmlReadTest;
END;
PRINT N'Creating table...';
CREATE TABLE dbo.XmlReadTest
(
ID INT NOT NULL IDENTITY(1, 1),
Col2 BIGINT,
Col3 UNIQUEIDENTIFIER,
Col4 DATETIME,
Col5 XML,
CONSTRAINT [PK_XmlReadTest] PRIMARY KEY CLUSTERED ([ID])
);
GO
DECLARE @MaxSets INT = 1000,
@CurrentSet INT = 1;
WHILE (@CurrentSet <= @MaxSets)
BEGIN
RAISERROR(N'Populating data (1000 sets of 1000 rows); Set # %d ...',
10, 1, @CurrentSet) WITH NOWAIT;
INSERT INTO dbo.XmlReadTest (Col2, Col3, Col4, Col5)
SELECT TOP 1000
CONVERT(BIGINT, CRYPT_GEN_RANDOM(8)),
NEWID(),
GETDATE(),
N'<test>'
+ REPLICATE(CONVERT(NVARCHAR(MAX), CRYPT_GEN_RANDOM(1), 2), 3750)
+ N'</test>'
FROM [master].[sys].all_columns sac1;
IF ((@CurrentSet % 100) = 0)
BEGIN
RAISERROR(N'Executing CHECKPOINT ...', 10, 1) WITH NOWAIT;
CHECKPOINT;
END;
SET @CurrentSet += 1;
END;
--
SELECT COUNT(*) FROM dbo.XmlReadTest; -- Verify that we have 1 million rows
-- O.P. states that the "clustered index fragmentation is close to 0%"
ALTER INDEX [PK_XmlReadTest] ON dbo.XmlReadTest REBUILD WITH (FILLFACTOR = 90);
CHECKPOINT;
--
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;
SET STATISTICS IO, TIME ON;
SELECT TOP 1000 * FROM dbo.XmlReadTest;
SET STATISTICS IO, TIME OFF;
/*
Scan count 1, logical reads 21, physical reads 1, read-ahead reads 4436,
lob logical reads 5676, lob physical reads 1, lob read-ahead reads 3967.
SQL Server Execution Times:
CPU time = 171 ms, elapsed time = 8329 ms.
*/
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.
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;
SET STATISTICS IO, TIME ON;
SELECT TOP 1000 ID, Col2, Col3, Col4 FROM dbo.XmlReadTest;
SET STATISTICS IO, TIME OFF;
/*
Scan count 1, logical reads 21, physical reads 1, read-ahead reads 4436,
lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 1666 ms.
*/
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:
~ 90% halaman tabel adalah LOB_DATA
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.