XML 인덱스로 매우 이상한 성능


32

내 질문은 이것을 기반으로합니다 : https : //.com/q/35575990/5089204

거기에 대한 답변을 제공하기 위해 다음 테스트 시나리오를 수행했습니다.

테스트 시나리오

먼저 테스트 테이블을 만들고 100.000 행으로 채 웁니다. 난수 (0 ~ 1000)는 각 난수에 대해 ~ 100 개의 행으로 이어져야합니다. 이 숫자는 varchar col에 입력되며 XML의 값으로 사용됩니다.

그런 다음 OP와 같은 호출을 수행하면 .exist () 및 .nodes ()가 필요하지만 두 번째에는 약간의 이점이 있지만 5-6 초가 걸립니다. 캐시 된 결과 나 계획을 통한 오 탐지 (false positive)를 피하기 위해 두 번의 순서로 전화를 걸었습니다. 두 번째는 교체 된 순서로, 검색 매개 변수가 약간 변경되고 전체 경로 대신 "// item"으로 변경되었습니다.

그런 다음 XML 인덱스를 만들고 동일한 호출을 수행합니다.

지금-정말 놀랐던 점! - .nodes으로 전체 경로 훨씬 느린 (8 초)보다도 있지만은 .exist()으로 0.5 초까지이고 전체 경로 에도 다운 0.10 초. (반면 .nodes()짧은 경로가 더 나은,하지만 여전히 멀리 뒤에 .exist())

질문 :

내 자신의 테스트는 짧게 나타납니다. XML 인덱스는 데이터베이스를 크게 손상시킬 수 있습니다. 그것들은 일의 속도를 늦출 수 있지만 (s. 편집 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가 중간 랩톱에 로컬로 설치된 결과 중 하나입니다 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

더 큰 XML로 테스트 2 편집

TT의 제안에 따르면 위의 XML을 사용했지만 item약 450 항목에 도달 하도록 -nodes를 복사했습니다 . 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 !!!

답변:


33

여기에 많은 일이 일어나고 있으므로 우리는 이것이 어디로 인도되는지 알아야합니다.

먼저, SQL Server 2012와 SQL Server 2014 간의 타이밍 차이는 SQL Server 2014의 새로운 카디널리티 추정기 때문입니다. SQL Server 2014의 추적 플래그를 사용하여 이전 추정기를 강제 실행할 수 있으며 동일한 타이밍을 볼 수 있습니다 SQL Server 2012와 같은 SQL Server 2014의 특성.

하나의 행에 대해 XML에 둘 이상의 일치하는 요소가있는 경우 동일한 결과를 리턴하지 않으므로 nodes()vs를 비교하는 exist()것은 공평하지 않습니다. 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에서 XML 인덱스가없는 nodes()쿼리는 위 의 수정 된 버전의 쿼리를 사용하는 데 6 초가 걸립니다 . 전체 경로 또는 짧은 경로를 사용하는 것에는 차이가 없습니다. XML 인덱스를 사용하면 전체 경로 버전이 가장 빠르며 5ms가 걸리고 짧은 경로를 사용하는 데 약 500ms가 걸립니다. 쿼리 계획을 검토하면 차이점이있는 이유를 알 수 있지만 짧은 버전은 짧은 경로를 사용할 때 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 인덱스를 만들면 내부 테이블에 클러스터되지 않은 인덱스가 만들어지며 생성 한 보조 인덱스의 유형에 따라 다른 열과 열 순서를 갖습니다.

에서 의 XML INDEX (Transact-SQL)를 :

VALUE
키 열이 기본 XML 인덱스의 노드 값 및 경로 인 열에 보조 XML 인덱스를 작성합니다.

PATH
기본 XML 인덱스의 경로 값과 노드 값을 기반으로하는 열에 보조 XML 인덱스를 만듭니다. PATH 보조 인덱스에서 경로 및 노드 값은 경로를 검색 할 때 효율적으로 검색 할 수있는 키 열입니다.

PROPERTY
기본 XML 인덱스의 열 (PK, 경로 및 노드 값)에 보조 XML 인덱스를 만듭니다. 여기서 PK는 기본 테이블의 기본 키입니다.

따라서 PATH 색인을 작성할 때 해당 색인의 첫 번째 열은 경로 표현식이고 두 번째 열은 해당 노드의 값입니다. 실제로 경로는 일종의 압축 형식으로 저장되고 반대로됩니다. 역으로 저장되므로 짧은 경로 표현식을 사용하는 검색에 유용합니다. 당신의 짧은 경로 경우 당신은 검색 //item/value/@string, //item/@name//item. 경로가 열 반전되어 저장되기 때문에, SQL Server를 함께 추구 범위를 사용할 수있는 like = '€€€€€€%경우 €€€€€€경로가 반대입니다. 전체 경로를 사용하는 like경우 전체 경로가 열에 인코딩되고 검색 술어에서도 값 을 사용할 수 있으므로 사용할 이유가 없습니다 .

당신의 질문 :

언제 XML 인덱스를 작성해야합니까?

최후의 수단으로. where 절에서 필터링하기 위해 XML 내부의 값을 사용할 필요가 없도록 데이터베이스를 설계하는 것이 좋습니다. 이를 수행해야한다는 것을 사전에 알고있는 경우 특성 승격 을 사용하여 필요한 경우 색인화 할 수있는 계산 열을 작성할 수 있습니다. SQL Server 2012 SP1부터는 선택적 XML 인덱스도 사용할 수 있습니다. 배후의 작업은 일반 XML 인덱스와 거의 동일하며 인덱스 정의에 경로 식을 지정하고 일치하는 노드 만 인덱스합니다. 그렇게하면 많은 공간을 절약 할 수 있습니다.

색인이없는 .nodes ()가없는 이유보다 더 나쁜 이유는 무엇입니까?

테이블에 XML 인덱스가 만들어지면 SQL Server는 항상 해당 인덱스 (내부 테이블)를 사용하여 데이터를 가져옵니다. 옵티마이 저가 빠르거나 빠르지 않은 것에 대해 말하기 전에 결정이 이루어집니다. 옵티 마이저에 대한 입력은 다시 작성되므로 내부 테이블을 사용하고 이후 일반 쿼리에서와 같이 최선을 다하는 것이 옵티 마이저에게 달려 있습니다. 인덱스가 사용되지 않으면 대신 사용되는 몇 가지 테이블 반환 함수가 있습니다. 결론은 테스트하지 않고 무엇이 더 빠를 지 알 수 없다는 것입니다.

어떻게 부정적인 영향을 피할 수 있습니까?

테스팅


2
차이점 .nodes().exist()설득력 에 대한 당신의 생각 . 또한 색인 full path search이 더 빠르다 는 사실은 이해하기 쉬운 것 같습니다. 이것은 의미 : 당신은 XML 인덱스를 만들 경우, 당신은해야한다 항상 어떤 일반적인 XPath를 (에 부정적인 영향을 인식 //하거나 *또는 ..또는 [filter]또는 아무것도 그냥 일반 XPath는 ...). 사실 당신은 전체 경로 만 사용해야합니다. – 꽤 훌륭한 추첨 ...
Shnugo
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.