【问题标题】:Selective XML index query plan选择性 XML 索引查询计划
【发布时间】:2015-01-18 17:09:06
【问题描述】:

我目前正在探索 SQL Server XML 列和选择性索引以满足我们的需要。为此,我创建了名为Incidents 的表并创建了Selective IndexSecondary selective Indexes(下面的脚本)。

当我运行以下查询时,它确实使用了选择性索引,但查询计划对严重性列数据执行IS NOT NULL 谓词,然后对其进行排序。当表中的数据很大时,这会显着降低查询的性能。我已经看到表中有 400 万行,完成以下查询需要大约 20 秒。

我在这里遗漏了什么吗?

select TOP 100 Data.value('(/Incident/Severity)[1]', 'int') AS Severity,

Data.value('(/Incident/OwningTenantId)[1]', 'VARCHAR(800)') AS OwningTenantId,

Data.value('(/Incident/OwningTeamId)[1]', 'NVARCHAR(800)') AS OwningTeamId

FROM Incidents

WHERE Data.value('(/Incident/Severity)[1]', 'int') = 1
ORDER BY Data.value('(/Incident/OwningTenantId)[1]', 'NVARCHAR(800)')

索引:

CREATE TABLE [dbo].[Incidents](
    [id] [uniqueidentifier] NOT NULL,
    [Data] [xml] NOT NULL,
 CONSTRAINT [PK_Incidents] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

CREATE SELECTIVE XML INDEX sxi_Incident_Data ON Incidents(Data)
 FOR 
 (
 Severity = '/Incident/Severity' AS SQL int SINGLETON,
 OwningTeamId = '/Incident/OwningTeamId' AS SQL NVARCHAR(400) SINGLETON,
 OwningTenantId = '/Incident/OwningTenantId' AS SQL NVARCHAR(400) SINGLETON,
 id = '/Incident/_id' AS SQL BIGINT SINGLETON
 )

 GO

 create xml index sxi_secondary_severity on Incidents(Data)
 using xml index sxi_Incident_Data
 for (Severity);

 GO

 create xml index sxi_secondary_OwningTeamId on Incidents(Data)
 using xml index sxi_Incident_Data
 for (OwningTeamId); 

 GO

 create xml index sxi_secondary_OwningTenantId on Incidents(Data)
 using xml index sxi_Incident_Data
 for (OwningTenantId); 

 GO


 create xml index sxi_secondary_Id on Incidents(Data)
 using xml index sxi_Incident_Data
 for (id); 

 GO

示例 XML:

<Incident>
  <_id>123</_id>
  <Severity>3</Severity>
  <IncidentStatus>RESOLVED</IncidentStatus>
  <CreateDate>2014-05-04 05:43:58.317</CreateDate>
  <LastUpdateDate>2014-05-06 18:47:39.037</LastUpdateDate>
  <AlertSourceLocalId>20070</AlertSourceLocalId>
  <SourceIncidentId>35d0bfe4-ccb9-491f-a30c-ea7685ffe8c0</SourceIncidentId>
  <SourceCreateDate>2014-05-04 02:51:14.000</SourceCreateDate>
  <SourceCreatedBy>Someone</SourceCreatedBy>
  <SourceModifiedDate>2014-05-04 05:43:57.797</SourceModifiedDate>
  <SourceOrigin>Some Origin</SourceOrigin>
  <CorrelationId>correlatioid</CorrelationId>
  <RoutingId>Route123</RoutingId>
  <Datacenter>Unknown</Datacenter>
  <Environment>INT</Environment>
  <DeviceGroup>Devicegroup</DeviceGroup>
  <DeviceName>DeviceName</DeviceName>
  <RaisingEnvironment>PROD</RaisingEnvironment>
  <RaisingDatacenter>Unknown</RaisingDatacenter>
  <RaisingDeviceGroup>DEviceGroup</RaisingDeviceGroup>
  <RaisingDeviceName>FakeDevice</RaisingDeviceName>
  <PrimaryIncidentId>1234</PrimaryIncidentId>
  <RelatedLinksCount>0</RelatedLinksCount>
  <ExternalLinksCount>0</ExternalLinksCount>
  <HitCount>0</HitCount>
  <ChildCount>0</ChildCount>
  <Title>Some Title</Title>
  <ReproSteps>&#x0;</ReproSteps>
  <OwningTenantId>564</OwningTenantId>
  <OwningTeamId>123</OwningTeamId>
  <ResolveDate>2014-05-06 18:47:39.037</ResolveDate>
  <ResolvedBy>SomeOne</ResolvedBy>
  <MitigateDate>2014-05-06 18:45:55.403</MitigateDate>
  <MitigatedBy>Someone</MitigatedBy>
  <Mitigation>N/A</Mitigation>
  <IsNoise>0</IsNoise>
  <IsSecurityRisk>0</IsSecurityRisk>
  <IsCustomerImpacting>0</IsCustomerImpacting>
  <OriginatingTenantId>10066</OriginatingTenantId>
  <ImpactStartDate>2014-05-01 23:31:22.000</ImpactStartDate>
  <RootCauseNeedsInvestigation>0</RootCauseNeedsInvestigation>
  <ConnectorTenantId>10066</ConnectorTenantId>
  <RelationshipId>1852546</RelationshipId>
  <SuppressAutoUpdate>0</SuppressAutoUpdate>
</Incident>

复制: 创建表索引

-- Create Table 
IF(EXISTS(SELECT * FROM sys.tables WHERE [Name] = 'XmlTable' AND [Type] = 'U'))
BEGIN
    DROP TABLE XmlTable
END

CREATE TABLE [dbo].[XmlTable](
    [id] [uniqueidentifier] NOT NULL,
    [Data] [xml] NULL
PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

-- Populate Data
DECLARE @i INT  = 0
DECLARE @XML NVARCHAR(MAX), 
        @Severity INT, 
        @OwningTeamId VARCHAR(400), 
        @OwningTenantId VARCHAR(400), 
        @IncidentStatus varchar(100),
        @Mod SMALLINT
WHILE @i < 500
BEGIN   
    SET @i = @i + 1
    SET @Mod = @i % 3

    SELECT @Severity = @Mod + 1,
           @OwningTeamId = 'OwningTeam' + CAST(@Mod AS VARCHAR),    
           @OwningTenantId = 'OwningTenantId' + CAST(@Mod AS VARCHAR),
           @IncidentStatus = CASE @Mod 
                                WHEN 0 THEN 'Active' 
                                WHEN 1 THEN 'Resolved' 
                                WHEN 2 THEN 'Closed' 
                             END    
    SET @XML = 
    '<Incident>' +
        '<_id>' + CAST(@i AS VARCHAR) + '</_id>' +
        '<Severity>' + CAST(@Severity AS VARCHAR) + '</Severity>' +
        '<OwningTeamId>' + @OwningTeamId + '</OwningTeamId>' +
        '<OwningTenantId>' + @OwningTenantId + '</OwningTenantId>' +
        '<IncidentStatus>' + @IncidentStatus + '</IncidentStatus>' +
    '</Incident>'
    INSERT INTO XmlTable
    SELECT NEWID(), @XML
END


-- Creat Indices
CREATE SELECTIVE XML INDEX [sxi_Data] ON [dbo].[XmlTable]
(
    [Data]
)
FOR
(
[Severity] = '/Incident/Severity' as SQL [int] SINGLETON , 
[OwningTeamId] = '/Incident/OwningTeamId' as SQL [nvarchar](400) SINGLETON , 
[OwningTenantId] = '/Incident/OwningTenantId' as SQL [nvarchar](400) SINGLETON , 
[id] = '/Incident/_id' as SQL [bigint] SINGLETON , 
[TicketStatus] = '/Incident/IncidentStatus' as SQL [nvarchar](100) SINGLETON 
)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
GO

CREATE XML INDEX [sxi_secondary_Id] ON [dbo].[XmlTable]
(
    [Data]
)USING XML INDEX [sxi_Data] FOR (
[id]
) 
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
GO

CREATE XML INDEX [sxi_secondary_OwningTeamId] ON [dbo].[XmlTable]
(
    [Data]
)USING XML INDEX [sxi_Data] FOR (
[OwningTeamId]
) 
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
GO

USE [XMLDocuemntStore]
GO


CREATE XML INDEX [sxi_secondary_OwningTenantId] ON [dbo].[XmlTable]
(
    [Data]
)USING XML INDEX [sxi_Data] FOR (
[OwningTenantId]
) 
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
GO

USE [XMLDocuemntStore]
GO


CREATE XML INDEX [sxi_secondary_severity] ON [dbo].[XmlTable]
(
    [Data]
)USING XML INDEX [sxi_Data] FOR (
[Severity]
) 
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
GO

示例查询:检查右侧的查询计划。

select TOP 100 Data.value('(/Incident/Severity)[1]', 'int') AS Severity
FROM XmlTable
WHERE Data.value('(/Incident/Severity)[1]', 'int') = 1
ORDER BY Data.value('(/Incident/OwningTenantId)[1]', 'NVARCHAR(800)')

【问题讨论】:

  • 这是什么版本的 SQL Server? 2012 年还是 2014 年?
  • 有人吗?我需要了解这种行为以及为什么查询计划如此不影响大量记录的性能?
  • 首先,目前还没有很多关于 XML 选择性索引的经验。其次,我认为我们需要查看两个查询计划来尝试理解这一点:一种是它现在在 20 秒超过 400 万行的情况下工作的方式,另一种方式是它没有进行这种比较并按您的预期执行。或者,如果您可以向我们提供一些代码或 SQL Fiddle 来重现该行为,我们或许可以提供帮助。
  • 我已经用脚本编辑了原始问题来解决问题
  • HI RBarryYoung,代码 sn-p 是否有助于重现问题...

标签: sql-server tsql sql-server-2012 sql-execution-plan


【解决方案1】:

由于 XPATH 查询中的 [1],因此需要 SORT TOP N。要摆脱这种情况,您需要确保 SQL Server 所需的 xml 元素仅在事件元素中出现一次。为此,您需要使用 XSD 文档强烈键入您的 XML。您可以像这样创建一个:

CREATE XML SCHEMA COLLECTION Incident_XSD AS
N'<?xml version="1.0" encoding="UTF-16"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="Incident">
    <xs:complexType>
      <xs:sequence>
        <xs:element type="xs:int" name="_id" />
        <xs:element type="xs:int" name="Severity" />
        <xs:element type="xs:string" name="OwningTeamId" />
        <xs:element type="xs:string" name="OwningTenantId" />
        <xs:element type="xs:string" name="IncidentStatus"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>' ;
GO

像这样在你的表定义中使用它

[Data] [xml](Incident_XSD) NULL

现在以下查询有效

select TOP 100 Data.value('/Incident[1]/Severity', 'int') AS Severity
FROM XmlTable
WHERE Data.value('/Incident[1]/Severity', 'int') = 1
ORDER BY Data.value('/Incident[1]/OwningTenantId', 'NVARCHAR(800)')

在一秒或两秒内返回表中的一百万行。

PS:您可能需要重新考虑使用 GUID 作为主键

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-01-22
    • 1970-01-01
    • 2018-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-22
    • 1970-01-01
    相关资源
    最近更新 更多