XML索引的性能非常奇怪


32

我的问题基于此:https : //stackoverflow.com/q/35575990/5089204

为了给出答案,我做了以下测试场景。

测试场景

首先,我创建一个测试表并用100.000行填充它。一个随机数(0到1000)应导致每个随机数约100行。此数字放入varchar列中,并作为XML中的值。

然后,我像OP那样进行呼叫,需要使用.exist()和.nodes()进行第二次调用,但是第二步的优势不大,但是两者都需要5到6秒钟。实际上,我进行了两次调用:第二次以交换顺序进行,搜索参数略有变化,并使用“ // item”而不是完整路径,以避免通过缓存的结果或计划产生误报。

然后创建一个XML索引并执行相同的调用

现在-真正让我惊讶的是!- .nodes具有完整路径的速度比以前(9秒)要慢得多,但是.exist()降低到半秒,而具有完整路径的时间甚至可以降低到约0.10秒。(同时.nodes()具有短的路径比较好,但仍远远落后于.exist()

问题:

我自己的测试简而言之:XML索引会极大地破坏数据库。它们可以极大地加快处理速度(例如,编辑2),但也会降低查询速度。我想了解它们的工作原理...什么时候应该创建XML索引?为什么.nodes()有了索引会比没有索引更糟糕?如何避免负面影响?

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;

编辑1-结果

这是在中型笔记本电脑上本地安装SQL Server 2012的结果。在此测试中,我无法重现对SQL Server 2012的极端负面影响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

编辑2使用更大的XML进行测试

根据TT的建议,我使用了上面的XML,但是复制了item-nodes来达到大约450个项目。我让点击节点在XML中的位置很高(因为我认为那.exist()会在第一次点击时停止,而.nodes()会继续)

创建XML索引会将 mdf文件炸毁到〜21GB〜18GB似乎属于索引(!!!)

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 !!!

Answers:


33

当然这里发生了很多事情,所以我们只需要看看这将导致什么。

首先,SQL Server 2012和SQL Server 2014之间的时间差异是由于SQL Server 2014中新的基数估计器引起的。您可以在SQL Server 2014中使用跟踪标志来强制使用旧的估计器,然后您将看到相同的计时与SQL Server 2012中一样的SQL Server 2014中的特性。

比较nodes()vs exist()是不公平的,因为如果XML中一行的匹配元素不止一个,它们将不会返回相同的结果。exist()会从基本表中返回一行,而nodes()可能会给您基础表中每行返回的一行以上。
我们知道数据,但是SQL Server却不知道,因此必须建立一个将其考虑在内的查询计划。

为了使nodes()查询等同于exist()查询,您可以执行以下操作。

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

对于这样的查询,使用nodes()or或之间没有区别exist(),这是因为SQL Server为两个不使用索引的版本构建几乎相同的计划,并且在使用索引时构建完全相同的计划。对于SQL Server 2012和SQL Server 2014都是如此。

对我来说,在SQL Server 2012中,使用上述nodes()查询的修改版本,没有XML索引的查询将花费6秒钟。使用完整路径或短路径没有区别。使用XML索引时,完整路径版本最快,需要5毫秒,而使用短路径大约需要500毫秒。检查查询计划将告诉您为什么有区别,但简短的版本是当您使用短路径时,SQL Server在短路径上的索引中查找(使用的范围查找like),并在丢弃该行之前返回700000行。值不匹配。使用完整路径时,SQL Server可以直接将路径表达式与节点的值一起使用来进行查找,并且从头开始仅返回105行。

使用SQL Server 2014和新的基数估计器,使用XML索引时,这些查询没有区别。在不使用索引的情况下,查询所花费的时间仍然相同,但是是15秒。当使用新的东西时,显然没有改进。

由于我将查询修改为等价的,因此不确定我是否完全忘记了您的问题的实质,但这就是我现在所相信的。

为什么nodes()使用XML索引的查询(原始版本)比不使用索引的查询慢得多?

好吧,答案是SQL Server查询计划优化器做得不好,并且引入了假脱机运算符。我不知道为什么,但是好消息是,SQL Server 2014中新的基数估计器
已不复存在。无论使用哪种基数估计器,没有适当的索引查询都将花费约7秒的时间。使用索引时,旧的估算器(SQL Server 2012)需要15秒,而新的估算器(SQL Server 2014)大约需要2秒。

注意:以上发现对您的测试数据有效。可能会有一个完全不同的故事来告诉您是否更改XML的大小,形状或形式。如果不对表中实际包含的数据进行测试,就无法确定。

XML索引如何工作

SQL Server中的XML索引被实现为内部表。主XML索引使用基表的主键加上节点ID列(总共12列)创建表。它每行将有一行,element/node/attribute etc.因此根据存储的XML的大小,表当然可以变得很大。有了主XML索引后,SQL Server可以使用内部表的主键为基表中的每一行定位XML节点和值。

辅助XML索引分为三种类型。创建二级XML索引时,内部表上会创建一个非聚集索引,并且根据您创建的二级索引的类型,它会具有不同的列和列顺序。

CREATE XML INDEX(Transact-SQL)

VALUE
在其中主字段(节点值和路径)为主XML索引的列上创建辅助XML索引。

PATH
在基于主XML索引中的路径值和节点值构建的列上创建辅助XML索引。在PATH二级索引中,路径和节点值是关键列,可在搜索路径时实现高效查找。

属性
在主XML索引的列(PK,路径和节点值)上创建辅助XML索引,其中PK是基表的主键。

因此,当您创建PATH索引时,该索引中的第一列是路径表达式,第二列是该节点中的值。实际上,路径以一种压缩格式存储并反转。反向存储它使它在使用短路径表达式的搜索中很有用。在您的短途旅行中//item/value/@string,您搜索了//item/@name//item。由于路径在列存储的逆转,SQL Server可以使用一系列带有寻求like = '€€€€€€%其中€€€€€€的路径是相反的。使用完整路径时,没有必要使用,like因为整个路径都在该列中进行了编码,并且该值也可以在搜索谓词中使用。

您的问题

什么时候应该创建一个XML索引?

作为万不得已的最后手段。最好设计数据库,这样就不必使用XML内的值在where子句中进行过滤。如果事先知道需要执行此操作,则可以使用属性提升来创建一个计算列,可以在需要时进行索引。从SQL Server 2012 SP1开始,您还可以使用选择性XML索引。幕后的工作原理与常规XML索引几乎相同,只是您在索引定义中指定了路径表达式,并且仅对匹配的节点进行了索引。这样,您可以节省很多空间。

为什么带有索引的.nodes()会比没有索引更糟糕?

当在表上创建XML索引时,SQL Server将始终使用该索引(内部表)来获取数据。该决定是在优化程序对什么是快速和什么不是快速做出决定之前做出的。优化器的输入将被重写,以便它使用内部表,然后由优化器决定是否像常规查询一样尽力而为。当不使用索引时,将使用几个表值函数。最重要的是,如果不进行测试,您将无法确定什么会更快。

如何避免负面影响?

测试中


2
您对的差异的想法.nodes().exist()被说服。同样,索引with full path search更快的事实似乎也很容易理解。这将意味着:如果创建XML索引,则必须始终意识到任何通用XPath(//*..[filter]或任何不只是普通Xpath ...)的负面影响。实际上,您应该只使用完整的路径-相当不错的
后撤
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.