XML alanının varlığı, tablo verilerinin çoğunun LOB_DATA sayfalarında bulunmasına neden olur (aslında tablo sayfalarının ~% 90'ı LOB_DATA'dır).
Yalnızca XML sütununun tabloda olması bu etkiye sahip değildir. Bu XML varlığı verilerine , belirli koşullar altında , bir satırın verilerinin bir kısmının LOB_DATA sayfalarında, satır kapalı saklanmasına neden olur. Ve biri (veya belki de birkaç ;-) duh olduğunu iddia edebilirken, XML
sütun gerçekten XML verilerinin olacağını ima eder, ancak XML verilerinin satır dışında depolanması gerektiği garanti edilmez: satır hemen hemen doldurulmadıkça XML verileri olmasının dışında, küçük belgeler (8000 bayta kadar) sıraya sığabilir ve hiçbir zaman LOB_DATA sayfasına gitmeyebilir.
sayfaların boyutu nedeniyle değil, aynı zamanda tabloda çok sayıda LOB_DATA sayfa olduğunda SQL Server kümelenmiş dizini etkili bir şekilde tarayamıyor çünkü LOB_DATA sayfalarının yavaş taramalara neden olabileceğini düşünerek doğru mu?
Tarama, tüm satırlara bakmayı ifade eder. Tabii ki, bir veri sayfası okunduğunda, sütunların bir alt kümesini seçmiş olsanız bile tüm satır içi veriler okunur. LOB verileriyle fark, o sütunu seçmezseniz, satır dışı verilerin okunmayacağıdır. Bu nedenle, SQL Server'ın bu Kümelenmiş Dizini ne kadar verimli bir şekilde tarayabileceği hakkında bir sonuç çıkarmak gerçekten doğru değildir, çünkü tam olarak test etmediniz (veya yarısını test ettiniz). XML sütununu içeren tüm sütunları seçtiniz ve belirttiğiniz gibi, verilerin çoğunun bulunduğu yer burasıdır.
Bu nedenle, SELECT TOP 1000 *
testin yalnızca arka arkaya bir dizi 8k veri sayfasını okumadığını, bunun yerine her satırdaki diğer konumlara atladığını zaten biliyoruz . Bu LOB verilerinin tam yapısı, ne kadar büyük olduğuna bağlı olarak değişebilir. Burada gösterilen araştırmaya dayanarak ( Varchar, Varbinary, vb. Gibi (MAX) Tipler için LOB İşaretçisinin Boyutu Nedir? ), İki tip off-line LOB tahsisi vardır:
- Satır İçi Kök - 8001 ve 40.000 (gerçekten 42.000) bayt arasındaki veriler için, alan izin verirse, doğrudan LOB sayfalarına yönlendiren 1 ila 5 işaretçi (24 - 72 bayt) olacaktır.
- TEXT_TREE - 42.000 baytın üzerindeki veriler için veya 1 ila 5 işaretçi sıraya sığmazsa, LOB sayfalarına işaretçiler listesinin başlangıç sayfasına yalnızca 24 baytlık bir işaretçi (ör. text_tree "sayfası).
Bu iki durumlardan biri her zaman oluştuğu almak 8000 bayt üzerinde ya da sadece içinde satır uymayan LOB veri. PasteBin.com ( LOB tahsislerini ve okumalarını test etmek için T-SQL komut dosyası) bir test komut dosyası ( LOB tahsislerinin 3 tipini (verilerin boyutuna bağlı olarak) ve bunların her birinin mantıksal ve fiziksel okumalar. Sizin durumunuzda, XML verileri gerçekten satır başına 42.000 bayttan azsa, hiçbiri (veya çok azı) en az verimli TEXT_TREE yapısında olmamalıdır.
SQL Server'ın bu Kümelenmiş Dizini ne kadar hızlı tarayabileceğini test etmek istiyorsanız, bunu yapın SELECT TOP 1000
ancak bu XML sütununu içermeyen bir veya daha fazla sütun belirtin . Bu sonuçlarınızı nasıl etkiler? Biraz daha hızlı olmalı.
böyle bir tablo yapısına / veri modeline sahip olmak makul kabul edilir mi?
Gerçek tablo yapısının ve veri modelinin eksik bir tanımına sahip olduğumuz göz önüne alındığında, bu eksik detayların ne olduğuna bağlı olarak herhangi bir cevap optimal olmayabilir. Bunu göz önünde bulundurarak, tablo yapınız veya veri deseniniz hakkında açıkça mantıksız bir şey olmadığını söyleyebilirim.
(Ac # app) XML 20KB ~ 2.5KB sıkıştırabilir ve LOB veri sayfalarının kullanımını önleyerek VARBINARY sütununda saklayabilirsiniz. Bu, testlerimde 20x SEÇ seçiyor.
Bu, tüm sütunları ve hatta yalnızca XML verilerini (şimdi VARBINARY
içeri) seçmeyi daha hızlı hale getirdi, ancak aslında "XML" verilerini seçmeyen sorguları incitiyor. Diğer sütunlarda yaklaşık 50 baytınız varsa ve FILLFACTOR
100'lük bir değeriniz varsa , o zaman:
Sıkıştırma Yok: 15k XML
veri, 2 Satır Kökü için 2 işaretçi gerektiren 2 LOB_DATA sayfası gerektirmelidir. İlk işaretçi 24 bayt ve ikincisi 12'dir ve XML verileri için satırda depolanan toplam 36 bayt için. Toplam satır boyutu 86 bayttır ve bu satırların yaklaşık 93'ünü 8060 baytlık bir veri sayfasına sığdırabilirsiniz. Bu nedenle, 1 milyon satır 10.753 veri sayfası gerektirir.
Özel Sıkıştırma: 2,5 bin VARBINARY
veri sıraya sığar. Toplam satır boyutu 2610 (2,5 * 1024 = 2560) bayttır ve bu satırlardan yalnızca 3 tanesini 8060 baytlık bir veri sayfasına sığdırabilirsiniz. Bu nedenle, 1 milyon satır 333.334 veri sayfası gerektirir.
Ergo, özel sıkıştırma uygulayarak Kümelenmiş Dizin için veri sayfalarında 30 kat artış sağlar . Yani, Kümelenmiş Dizin taraması kullanan tüm sorguların okunması gereken yaklaşık 322.500 veri sayfası daha var . Bu tür bir sıkıştırma yapmanın ek sonuçları için lütfen aşağıdaki ayrıntılı bölüme bakın.
Ben performans dayalı herhangi bir yeniden düzenleme yapmaya karşı dikkat ediyorum SELECT TOP 1000 *
. Bu, uygulamanın bile yayınlayacağı bir sorgu değildir ve potansiyel olarak gereksiz optimizasyon (lar) için tek temel olarak kullanılmamalıdır.
Denemek için daha ayrıntılı bilgi ve daha fazla test için lütfen aşağıdaki bölüme bakın.
Bu soruya kesin bir cevap verilemez, ancak en azından bizi tam konuyu (ideal olarak kanıtlara dayanarak) bulmaya daha da yakınlaştırmaya yardımcı olmak için biraz ilerleme kaydedebilir ve ek araştırma önerebiliriz.
Ne biliyoruz:
- Tabloda yaklaşık 1 milyon satır var
- Tablo boyutu yaklaşık 15 GB
- Tablo birini içeren
XML
sütunu ve türleri çeşitli diğer sütunları: INT
, BIGINT
, UNIQUEIDENTIFIER
, "vb"
XML
"boyut" sütunu ortalama olarak yaklaşık 15k
- Çalıştırdıktan sonra
DBCC DROPCLEANBUFFERS
, aşağıdaki sorgunun tamamlanması 20-25 saniye sürer:SELECT TOP 1000 * FROM TABLE
- Kümelenmiş Dizin taranıyor
- Kümelenmiş Dizindeki Parçalanma% 0'a yakın
Bildiğimizi düşündüğümüz:
- Bu sorguların dışında başka disk etkinliği yok. Emin misiniz? Başka kullanıcı sorgusu olmasa bile, arka plan işlemleri yapılıyor mu? Aynı makinede çalışan ve bazı IO'ları alabilen SQL Server harici işlemler var mı? Olmayabilir, ancak yalnızca sağlanan bilgilere dayanarak net değildir.
- 15 MB XML verisi döndürülüyor. Bu sayı neye dayanıyor? 1000 satırdan elde edilen bir tahmin, satır başına ortalama 15k XML verisi? Veya bu sorgu için alınanların programlı bir toplamı? Sadece bir tahminse, XML verilerinin dağılımı basit bir ortalama ile ima edilen şekilde bile olmayabileceğinden buna güvenmem.
XML Sıkıştırma yardımcı olabilir. Sıkıştırmayı .NET'te tam olarak nasıl yapardınız? Via GZipStream veya DeflateStream sınıfları? Bu sıfır maliyetli bir seçenek değildir. Kesinlikle bazı verileri büyük bir yüzde oranında sıkıştıracaktır, ancak her seferinde verileri sıkıştırmak / açmak için ek bir işleme ihtiyacınız olacağından daha fazla CPU gerektirecektir. Bu plan ayrıca aşağıdakileri yapma yeteneğinizi de tamamen ortadan kaldıracaktır:
- aracılığıyla sorgu XML veri
.nodes
, .value
, .query
, ve .modify
XML fonksiyonları.
XML verilerini indeksler.
XML
Veri türünün, öğe ve öznitelik adlarını bir sözlükte depolaması, her öğeye bir tamsayı dizin kimliği ataması ve ardından bu tamsayı kimliğini kullanması için , veri türünün zaten optimize edildiğini (XML'nin "çok fazla yedekli olduğunu belirttiğinizden) lütfen unutmayın. (dolayısıyla her kullanım için tam adı tekrarlamaz veya öğeler için kapanış etiketi olarak tekrarlamaz). Gerçek veriler ayrıca yabancı beyaz boşluğa sahiptir. Bu nedenle çıkarılan XML belgeleri orijinal yapılarını korumaz ve boş öğeler neden <element />
içeri girmiş gibi çıkarılır<element></element>
. Bu nedenle, GZip (veya başka bir şey) ile sıkıştırmadan elde edilen kazançlar, yalnızca beklenenden daha fazla iyileştirilebilecek ve büyük olasılıkla kaybına değmeyecek çok daha küçük bir yüzey alanı olan eleman ve / veya nitelik değerlerini sıkıştırarak bulunur. doğrudan yukarıda belirtildiği gibi yetenekler.
Lütfen XML verilerinin sıkıştırılmasının ve VARBINARY(MAX)
sonucun saklanmasının LOB erişimini ortadan kaldırmayacağını, yalnızca azaltacağını unutmayın. Satırdaki verilerin geri kalanının boyutuna bağlı olarak, sıkıştırılmış değer sıraya sığabilir veya yine de LOB sayfaları gerektirebilir.
Bu bilgi, yardımcı olmakla birlikte, neredeyse yeterli değildir. Sorgu performansını etkileyen birçok faktör vardır, bu yüzden neler olup bittiğinin çok daha ayrıntılı bir resmine ihtiyacımız var.
Bilmediklerimiz, ancak aşağıdakilere ihtiyacımız var:
SELECT *
Maddenin performansı neden önemlidir? Bu, kodda kullandığınız bir kalıp mı? Öyleyse neden?
- Yalnızca XML sütununu seçmenin performansı nedir? Sadece yaparsanız istatistik ve zamanlama nedir
SELECT TOP 1000 XmlColumn FROM TABLE;
?
Bu 1000 satırı döndürmek için gereken 20 - 25 saniyenin ne kadarı ağ faktörleriyle (veriyi telden alırken) ve müşteri faktörleriyle ne kadar ilgilidir (yaklaşık 15 MB'ı artı olmayan XML verilerini SSMS'deki ızgaraya veya muhtemelen diske kaydetme)?
Operasyonun bu iki yönünü çarpanlarına ayırmak bazen verileri döndürmemek suretiyle yapılabilir. Şimdi, bir Geçici Tablo veya Tablo Değişkeni seçmeyi düşünebiliriz, ancak bu sadece birkaç yeni değişken getirecektir (yani tempdb
, İşlem Günlüğü yazarları için disk G / Ç , tempdb verilerinin ve / veya günlük dosyasının otomatik olarak büyümesi, Tampon Havuzundaki boşluk vb.). Tüm bu yeni faktörler aslında sorgu süresini artırabilir. Bunun yerine, tipik olarak sütunları SQL_VARIANT
her yeni satırla (yani SELECT @Column1 = tab.Column1,...
) üzerine yazılan değişkenlere (uygun veri tipinin; değil ) depolarım .
ANCAK , bu DBA.StackExchange S ve C'de @PulWhite tarafından işaret edildiği gibi, Mantıksal LOB verilerine erişirken farklı araştırmalar, PasteBin'de yayınlanan kendi ek araştırmalarım ( LOB okumaları için çeşitli senaryoları test etmek için T-SQL betiği ) , LOB sürekli arasında erişilebilir değildir SELECT
, SELECT INTO
, SELECT @XmlVariable = XmlColumn
, SELECT @XmlVariable = XmlColumn.query(N'/')
, ve SELECT @NVarCharVariable = CONVERT(NVARCHAR(MAX), XmlColumn)
. Yani seçeneklerimiz burada biraz daha sınırlı, ama işte neler yapılabilir:
- SSMS veya SQLCMD.EXE içinde SQL Server çalıştıran sunucuda sorguyu yürüterek ağ sorunlarını dışlayın.
- Sorgu Seçenekleri -> Sonuçlar -> Izgara'ya gidip "Yürütmeden sonra sonuçları sil" seçeneğini işaretleyerek SSMS'deki istemci sorunlarını ortadan kaldırın. Bu seçeneğin mesajlar da dahil olmak üzere TÜM çıktıları önleyeceğini, ancak SSMS'nin her satır başına belleği ayırması ve ardından ızgaraya çizmesi için geçen süreyi göz ardı etmekte faydalı olabileceğini lütfen unutmayın.
Alternatif olarak, sqlCmd.exe aracılığıyla sorguyu yürütmek ve üzeri hiçbir yere gitmek için çıktı yönlendirmek olabilir: -o NUL:
.
- Bu sorgu ile ilişkili bir Bekleme Türü var mı? Evet ise, bu Bekleme Türü nedir?
Döndürülen sütunlar için gerçek veri boyutu nedir ? "TOP 1000" satırlarının toplam verilerin orantısız olarak büyük bir bölümünü içermesi durumunda, bu sütunun tüm tablodaki ortalama boyutu önemli değildir . TOP 1000 satırları hakkında bilgi edinmek istiyorsanız, bu satırlara bakın. Lütfen aşağıdakileri çalıştırın:XML
XML
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;
- Kesin tablo şema. Lütfen tüm dizinler dahil olmak üzere ifadenin tamamını
CREATE TABLE
belirtin.
- Sorgu planı? Bu gönderebileceğiniz bir şey mi? Bu bilgi muhtemelen hiçbir şeyi değiştirmeyecektir, ancak bunun yanlış olmayacağını ve yanlış olmayacağını tahmin etmekten daha iyi olmayacağını bilmek daha iyidir ;-)
- Veri dosyasında fiziksel / harici parçalanma var mı? Burada büyük bir faktör olmasa da, SSD veya Süper Pahalı SATA yerine "tüketici sınıfı SATA" kullandığınızdan, özellikle optimal olmayan sektörlerin etkisi, özellikle bu sektörlerin sayısı kadar, daha belirgin olacaktır. okunması gereken artış.
Aşağıdaki sorgunun kesin sonuçları nelerdir :
SELECT * FROM sys.dm_db_index_physical_stats(DB_ID(),
OBJECT_ID(N'dbo.SchemaName.TableName'), 1, 0, N'LIMITED');
GÜNCELLEME
Bana benzer bir deneyim yaşayıp yaşamadığımı görmek için bu senaryoyu yeniden oluşturmaya çalışmalıydım. Bu nedenle, birkaç sütunlu bir tablo oluşturdum (Sorudaki belirsiz açıklamaya benzer) ve sonra 1 milyon satırla doldurdum ve XML sütununda satır başına yaklaşık 15k veri var (aşağıdaki koda bakın).
Ne buldum SELECT TOP 1000 * FROM TABLE
ilk kez 8 saniye içinde tamamlanmış ve daha sonra her seferinde 2-4 saniye (evet, DBCC DROPCLEANBUFFERS
her SELECT *
sorgu çalıştırmadan önce yürütme ) olduğunu. Ve birkaç yaşındaki dizüstü bilgisayarım hızlı değil : SQL Server 2012 SP2 Developer Edition, 64 bit, 6 GB RAM, çift 2.5 Ghz Core i5 ve 5400 RPM SATA sürücü. Ayrıca SSMS 2014, SQL Server Express 2014, Chrome ve diğer pek çok şeyi çalıştırıyorum.
Sistemimin yanıt süresine bağlı olarak, 20-25 saniyelik yanıt süresinin nedenini daraltmaya yardımcı olmak için daha fazla bilgiye ihtiyacımız olduğunu (tablo ve veriler hakkında ayrıntılar, önerilen testlerin sonuçları vb.) Tekrarlayacağım. görüyorsunuz.
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.
*/
Ve, LOB olmayan sayfaları okumak için harcanan zamanı hesaba katmak istediğimiz için, XML sütunu (yukarıda önerdiğim testlerden biri) dışında tümünü seçmek için aşağıdaki sorguyu çalıştırdım. Bu, 1,5 saniyede oldukça tutarlı bir şekilde geri döner.
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.
*/
Sonuç (şimdilik)
Senaryonuzu yeniden oluşturma girişimime dayanarak, SATA sürücüsünü veya sıralı olmayan G / Ç'yi 20-25 saniyenin ana nedeni olarak gösterebileceğimizi düşünmüyorum, özellikle de hala XML sütunu dahil edilmediğinde sorgunun ne kadar hızlı döndüğünü bilmiyorum. Ve gösterdiğiniz çok sayıda Mantıksal Okuma (LOB olmayan) çoğaltmayı başaramadım, ancak bunun ve ifadesinin ışığında her satıra daha fazla veri eklemem gerektiğini hissediyorum :
Tablo sayfalarının ~% 90'ı LOB_DATA
sys.dm_db_index_physical_stats
Tablomda , her biri 15k'nin üzerinde XML verisi olan 1 milyon satır var ve 2 milyon LOB_DATA sayfası olduğunu gösteriyor. Kalan% 10 daha sonra 222k IN_ROW veri sayfası olacaktır, ancak bunlardan sadece 11.630'um var. Yani bir kez daha, gerçek tablo şeması ve gerçek veriler hakkında daha fazla bilgiye ihtiyacımız var.