XML diziniyle çok garip performans


32

Sorum şu: https://stackoverflow.com/q/35575990/5089204

Orada bir cevap vermek için aşağıdaki test senaryosunu yaptım.

Test senaryosu

İlk önce bir test masası oluşturup 100.000 satırla doldururum. Bir rastgele sayı (0 ila 1000) her rastgele sayı için ~ 100 satıra neden olmalıdır. Bu sayı bir varchar col'a ve XML'inize bir değer olarak koyulur.

Daha sonra OP gibi bir çağrı yapıyorum, bunun için .exist () ve .nodes () ile ikinci bir avantajı var, ancak her ikisi de 5 ila 6 saniye sürüyor. Aslında çağrıları iki kez yapıyorum: ikinci kez takaslı sırada ve hafifçe değiştirilmiş arama paraşütleriyle ve önbelleklenmiş sonuçlar veya planlar yoluyla yanlış pozitifleri önlemek için tam yol yerine "// item" ile.

Sonra bir XML dizini oluşturuyorum ve aynı çağrıları yapıyorum

Şimdi - beni gerçekten şaşırtan ne oldu! - .nodesile tam yol çok daha yavaş (9 saniye) daha önce ancak .exist()ile, yarım saniye kadar olan tam yolu da aşağı 0.10 ila yaklaşık sn. (ederken .nodes()ile kısa yol iyidir, ama yine de çok gerisinde .exist())

Sorular:

Kendi testlerim kısaca gündeme geldi: XML dizinleri bir veritabanını aşırı derecede havaya uçurabilir. İşleri aşırı hızlandırabilir (düzenleme 2), ancak sorgularınızı da yavaşlatabilir. Nasıl çalıştıklarını anlamak istiyorum ... Ne zaman bir XML dizini oluşturmalı? Neden .nodes()bir indeks ile olmadan daha kötü olabilir ? Olumsuz bir etki nasıl önlenebilir?

CREATE TABLE #testTbl(ID INT IDENTITY PRIMARY KEY, SomeData VARCHAR(100),XmlColumn XML);
GO

DECLARE @RndNumber VARCHAR(100)=(SELECT CAST(CAST(RAND()*1000 AS INT) AS VARCHAR(100)));

INSERT INTO #testTbl VALUES('Data_' + @RndNumber,
'<error application="application" host="host" type="exception" message="message" >
  <serverVariables>
    <item name="name1">
      <value string="text" />
    </item>
    <item name="name2">
      <value string="text2" />
    </item>
    <item name="name3">
      <value string="text3" />
    </item>
    <item name="name4">
      <value string="text4" />
    </item>
    <item name="name5">
      <value string="My test ' +  @RndNumber + '" />
    </item>
    <item name="name6">
      <value string="text6" />
    </item>
    <item name="name7">
      <value string="text7" />
    </item>
  </serverVariables>
</error>');

GO 100000

DECLARE @d DATETIME=GETDATE()
SELECT #testTbl.*
FROM #testTbl
CROSS APPLY XmlColumn.nodes('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') AS a(b);
SELECT CAST(GETDATE()-@d AS TIME) AS NodesFullPath_no_index;
GO

DECLARE @d DATETIME=GETDATE();
SELECT * 
FROM #testTbl
WHERE XmlColumn.exist('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') = 1;
SELECT CAST(GETDATE()-@d AS TIME) AS ExistFullPath_no_index;
GO

DECLARE @d DATETIME=GETDATE();
SELECT * 
FROM #testTbl
WHERE XmlColumn.exist('//item[@name="name5" and value/@string="My test 500"]') = 1;
SELECT CAST(GETDATE()-@d AS TIME) AS ExistShortPath_no_index;
GO

DECLARE @d DATETIME=GETDATE()
SELECT #testTbl.*
FROM #testTbl
CROSS APPLY XmlColumn.nodes('//item[@name="name5" and value/@string="My test 500"]') AS a(b);
SELECT CAST(GETDATE()-@d AS TIME) AS NodesShortPath_no_index;
GO

CREATE PRIMARY XML INDEX PXML_test_XmlColum1 ON #testTbl(XmlColumn);
CREATE XML INDEX IXML_test_XmlColumn2 ON #testTbl(XmlColumn) USING XML INDEX PXML_test_XmlColum1 FOR PATH;
GO

DECLARE @d DATETIME=GETDATE()
SELECT #testTbl.*
FROM #testTbl
CROSS APPLY XmlColumn.nodes('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') AS a(b);
SELECT CAST(GETDATE()-@d AS TIME) AS NodesFullPath_with_index;
GO

DECLARE @d DATETIME=GETDATE();
SELECT * 
FROM #testTbl
WHERE XmlColumn.exist('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') = 1;
SELECT CAST(GETDATE()-@d AS TIME) AS ExistFullPath_with_index;
GO

DECLARE @d DATETIME=GETDATE();
SELECT * 
FROM #testTbl
WHERE XmlColumn.exist('//item[@name="name5" and value/@string="My test 500"]') = 1;
SELECT CAST(GETDATE()-@d AS TIME) AS ExistShortPath_with_index;
GO

DECLARE @d DATETIME=GETDATE()
SELECT #testTbl.*
FROM #testTbl
CROSS APPLY XmlColumn.nodes('//item[@name="name5" and value/@string="My test 500"]') AS a(b);
SELECT CAST(GETDATE()-@d AS TIME) AS NodesShortPath_with_index;
GO

DROP TABLE #testTbl;

EDIT 1 - Sonuçlar

Bu, yerel olarak orta seviye bir dizüstü bilgisayarda kurulu olan SQL Server 2012 ile ortaya çıkan bir sonuçtur. Bu testte, son derece olumsuz olan etkiyi yeniden üretemedim NodesFullPath_with_index;

NodesFullPath_no_index    6.067
ExistFullPath_no_index    6.223
ExistShortPath_no_index   8.373
NodesShortPath_no_index   6.733

NodesFullPath_with_index  7.247
ExistFullPath_with_index  0.217
ExistShortPath_with_index 0.500
NodesShortPath_with_index 2.410

Daha büyük XML ile EDIT 2 Testi

TT'nin önerisine göre yukarıdaki XML'i kullandım, ancak item-node'ları yaklaşık 450 maddeye ulaşmak için kopyaladım . XML'de hit-node'un çok yükseğe çıkmasına izin verdim (çünkü bunun .exist()ilk hitde duracağını düşünüyorum , ancak .nodes()devam ederdi)

XML dizini oluşturmak, mdf dosyasını ~ 21GB'a düşürdü, ~ 18GB dizine ait görünüyor (!!!)

NodesFullPath_no_index    3min44
ExistFullPath_no_index    3min39
ExistShortPath_no_index   3min49
NodesShortPath_no_index   4min00

NodesFullPath_with_index  8min20
ExistFullPath_with_index  8,5 seconds !!!
ExistShortPath_with_index 1min21
NodesShortPath_with_index 13min41 !!!

Yanıtlar:


33

Burada çok şey oluyor, o yüzden bunun nereye gittiğini görmek zorundayız.

Öncelikle, SQL Server 2012 ve SQL Server 2014 arasındaki zamanlamadaki fark, SQL Server 2014'teki yeni kardinalite tahmincisinden kaynaklanmaktadır. Eski tahminciyi zorlamak için SQL Server 2014'te bir izleme bayrağı kullanabilirsiniz ve aynı zamanlamayı göreceksiniz. Server 2014’te olduğu gibi SQL Server 2014’teki özellikler.

Karşılaştırma nodes()vs exist()bir satır için XML birden fazla eşleşen eleman varsa onlar aynı sonucu döndürmez çünkü adil değil. exist()ne olursa olsun temel tablodan bir satır döndürür, oysa nodes()potansiyel olarak temel tablodaki her satır için döndürülen birden fazla satır verebilir.
Verileri biliyoruz, ancak SQL Server bunu dikkate almıyor ve bir sorgu planı oluşturması gerekmiyor.

Yapmak için nodes()sorgu eşdeğer exist()sorgusu, böyle bir şey yapabilirdi.

SELECT testTbl.*
FROM testTbl
WHERE EXISTS (
             SELECT *
             FROM XmlColumn.nodes('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') AS a(b)
             )

Bunun gibi bir sorguda, nodes()ya da kullanmak arasında bir fark yoktur exist()çünkü bunun nedeni, SQL Server'ın bir indeks kullanmayan iki versiyon için neredeyse aynı planı oluşturmasıdır ve indeks kullanıldığında tam olarak aynı planı oluşturur. Bu, hem SQL Server 2012 hem de SQL Server 2014 için geçerlidir.

Benim için SQL Server 2012'de, XML dizini olmayan sorguların nodes()yukarıdaki sorgunun değiştirilmiş halini kullanması 6 saniye sürüyor . Tam yol veya kısa yol arasında fark yoktur. XML dizini yerinde iken, tam yol sürümü en hızlıdır ve 5 ms sürer ve kısa yolu kullanarak yaklaşık 500 ms sürer. Sorgu planlarını incelemek size neden bir fark olduğunu söyleyecektir, ancak kısa sürüm kısa bir yol kullandığınızda, SQL Server kısa yoldaki dizinde arama yapar (bir dizi aramayı kullanarak like) ve bu satırları atmadan önce 700000 satır döndürür. değer ile eşleşmiyor. Tam yolu kullanırken, SQL Server arama yapmak için yol ifadesini doğrudan düğümün değeriyle birlikte kullanabilir ve sıfırdan çalışmak için yalnızca 105 satır döndürür.

SQL Server 2014 ve yeni kardinalty tahmincisi kullanılarak, XML dizini kullanılırken bu sorgularda hiçbir fark yoktur. Dizini kullanmadan sorgular yine de aynı süreyi alır ancak 15 saniyedir. Yeni şeyler kullanırken burada kesinlikle bir gelişme yok.

Sorguların gerçekte neyle ilgili olduğunu tamamen kaybettiğimden emin değilim, çünkü sorguları eşdeğer olacak şekilde değiştirdim, ama işte şimdi olduğuna inanıyorum.

nodes()XML dizini yerinde olan sorgu (orijinal sürüm) neden bir dizin kullanılmadığında önemli ölçüde yavaşlar?

Cevap, SQL Server sorgu planı iyileştiricisinin kötü bir şey yapması ve bir biriktirme işleci tanıtması. Nedenini bilmiyorum ama iyi haber şu ki, artık SQL Server 2014'teki yeni kardinalty tahmincisiyle artık orada değil.
Hangi kardinalite tahmincisinin kullanıldığına bakılmaksızın, dizin bulunmayan sorgu yaklaşık 7 saniye sürüyor. İndeks ile eski tahmin edicinin (SQL Server 2012) 15 saniyesini, yeni tahmincinin de (SQL Server 2014) 2 saniyesini alır.

Not: Yukarıdaki bulgular test verileriniz için geçerlidir. XML'in boyutunu, şeklini veya şeklini değiştirip değiştirmeyeceğinizi anlatan tamamen farklı bir hikaye olabilir. Gerçekten tablolarda bulunan verilerle test yapmadan kesin olarak bilmenin yolu yok.

XML dizinleri nasıl çalışır?

SQL Server'daki XML dizinleri iç tablo olarak uygulanır. Birincil XML dizini, temel tablonun birincil anahtarını ve toplam 12 sütunda düğüm kimliğini içeren tabloyu oluşturur. Tabii başına bir satır olacak, element/node/attribute etc.böylece tablonun sakladığı XML'in boyutuna bağlı olarak elbette gerçekten büyük olması sağlanacaktır . Yerinde bir birincil XML dizini olan SQL Server, temel tablodaki her satırın XML düğümlerini ve değerlerini bulmak için iç tablonun birincil anahtarını kullanabilir.

İkincil XML dizinleri üç tür halinde gelir. İkincil bir XML dizini oluşturduğunuzda, iç tabloda oluşturulan kümelenmemiş bir dizin vardır ve ne tür ikincil dizin oluşturduğunuza bağlı olarak, farklı sütunlara ve sütun sıralarına sahip olacaktır.

Gönderen XML INDEX (Transact-SQL) CREATE :

DEĞER
Birincil XML dizininin anahtar sütunlarının (düğüm değeri ve yol) olduğu sütunlarda ikincil bir XML dizini oluşturur.

PATH
Birincil XML dizinindeki yol değerleri ve düğüm değerleri üzerine inşa edilmiş sütunlar üzerinde ikincil bir XML dizini oluşturur. PATH ikincil dizininde, yol ve düğüm değerleri, yolları ararken etkili aramalara izin veren anahtar sütunlardır.

ÖZELLİK
PK'nin temel tablonun birincil anahtarı olduğu birincil XML dizininin sütunlarında (PK, yol ve düğüm değeri) ikincil bir XML dizini oluşturur.

Dolayısıyla bir PATH dizini oluşturduğunuzda, bu dizindeki ilk sütun yol ifadesi ve ikinci sütun o düğümdeki değerdir. Aslında, yol bir tür sıkıştırılmış biçimde saklanır ve tersine çevrilir. Tersine çevrilmiş olarak saklanması, kısa yol ifadeleri kullanarak yapılan aramalarda faydalı olmasını sağlar. Kısa yolunuzda //item/value/@string, //item/@nameve için arama yaptınız //item. Yol sütunda ters olarak depolandığından, SQL Server yolun tersi like = '€€€€€€%olduğu yerde bir aralık kullanabilir €€€€€€. Tam bir yol kullandığınızda like, yolun tamamı sütunda kodlandığından ve değer de arama yükleminde kullanılabildiğinden kullanmak için hiçbir neden yoktur .

Sorularınız :

Kişi ne zaman bir XML dizini oluşturmalıdır?

Hiç değilse son çare olarak. Veritabanınızı tasarlamak daha iyidir, böylece bir cümlede filtrelemek için XML içindeki değerleri kullanmak zorunda kalmazsınız. Önceden yapmanız gerektiğini biliyorsanız, gerektiğinde indeksleyebileceğiniz hesaplanmış bir sütun oluşturmak için mülk tanıtımını kullanabilirsiniz . SQL Server 2012 SP1'den bu yana seçmeli XML dizinleri de mevcut. Sahnenin arkasındaki çalışmalar, normal XML dizinleriyle hemen hemen aynıdır, yalnızca dizin ifadesinde yol ifadesini belirtirsiniz ve yalnızca eşleşen dizinler dizine eklenir. Bu şekilde çok fazla alan kazanabilirsiniz.

Neden bir indeksi olan .nodes () neden olmasın daha kötü olabilir?

Bir tabloda oluşturulan bir XML dizini olduğunda, SQL Server verileri almak için her zaman bu dizini (dahili tablolar) kullanır. Bu karar, optimizer'ın hızlı ve neyin hızlı olmadığı konusunda bir söz vermeden önce yapılır. En iyi duruma getiriciye giriş yeniden yazılır, böylece iç tabloları kullanır ve bundan sonra en iyi duruma getiriciyi normal bir sorguda olduğu gibi yapar. Dizin kullanılmadığında, bunun yerine kullanılan birkaç tablo değerli işlev vardır. Sonuç olarak, test etmeden neyin daha hızlı olacağını söyleyemezsiniz.

Olumsuz etkilerden nasıl kaçınabilir?

Test yapmak


2
Fark hakkındaki fikirleriniz .nodes()ve .exist()inandırıcı. Ayrıca endeks ile full path searchdaha hızlı olduğu gerçeğini anlamak kolay görünüyor. Bunun anlamı şudur: Bir XML dizini oluşturursanız, herhangi bir genel XPath ( veya veya veya yalnızca düz Xpath ... değil herhangi bir şey) üzerindeki olumsuz etkiden daima haberdar olmalısınız . Aslında sadece tam yolu kullanmalısın - oldukça iyi bir geri çekiliş ...//*..[filter]
Shnugo
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.