XML 필드가 있으면 대부분의 테이블 데이터가 LOB_DATA 페이지에 위치합니다 (실제로 테이블 페이지의 ~ 90 %가 LOB_DATA 임).
테이블에 XML 열만 있으면 효과가 없습니다. 그것은 XML의 존재이다 데이터 , 특정 조건 하에서 , 행의 데이터의 일부가 LOB_DATA 페이지에 행을 저장됩니다. 그리고 하나 (또는 아마도 여러 개의 ;-)는 duh라고 주장 할 수 있지만 XML
열은 실제로 XML 데이터가 있음을 암시하지만 XML 데이터가 행 외부에 저장되어 있어야한다고 보장하지는 않습니다. XML 데이터 이외의 작은 문서 (최대 8000 바이트)는 한 줄에 들어가고 LOB_DATA 페이지로 이동하지 않을 수 있습니다.
LOB_DATA 페이지는 크기 때문에뿐만 아니라 테이블에 많은 LOB_DATA 페이지가있을 때 SQL Server가 클러스터형 인덱스를 효과적으로 스캔 할 수 없기 때문에 느린 스캔을 일으킬 수 있다고 생각하고 있습니까?
스캐닝은 모든 행을 보는 것을 말합니다. 물론 데이터 페이지를 읽을 때 열의 서브 세트를 선택한 경우에도 모든 행 내 데이터를 읽습니다. LOB 데이터와의 차이점은 해당 열을 선택하지 않으면 행 외부 데이터를 읽을 수 없다는 것입니다. 따라서 SQL Server가이 클러스터형 인덱스를 정확하게 테스트하지 않았거나 정확히 테스트하지 않았기 때문에이 클러스터형 인덱스를 얼마나 효율적으로 스캔 할 수 있는지에 대한 결론을 내리는 것은 불공평합니다. XML 열을 포함하여 모든 열을 선택했으며 앞에서 언급했듯이 대부분의 데이터가있는 열을 선택했습니다.
따라서 우리는 이미 SELECT TOP 1000 *
테스트가 일련의 8k 데이터 페이지를 한 줄로 읽는 것이 아니라 각 행마다 다른 위치로 점프하는 것임을 이미 알고 있었습니다 . 해당 LOB 데이터의 정확한 구조는 데이터의 크기에 따라 달라질 수 있습니다. 여기에 표시된 연구 ( Varchar, Varbinary 등의 (MAX) 유형에 대한 LOB 포인터의 크기는 얼마입니까? )에 따라 두 가지 유형의 오프라인 LOB 할당이 있습니다.
- 인라인 루트-8001-40,000 (실제 42,000) 바이트 사이의 데이터에 대해 공간이 허용되는 경우 LOB 페이지를 직접 가리키는 1-5 개의 포인터 (24-72 바이트) IN ROW가 있습니다.
- TEXT_TREE-42,000 바이트를 초과하는 데이터의 경우 또는 1-5 개의 포인터가 행에 맞지 않을 경우 LOB 페이지에 대한 포인터 목록의 시작 페이지에 24 바이트 포인터 만 있습니다 (예 : " text_tree "페이지).
이 두 가지 상황 중 하나는 8000 바이트를 초과하거나 행에 맞지 않는 LOB 데이터를 검색 할 때마다 발생합니다 . PasteBin.com ( LOB 할당 및 읽기 테스트를위한 T-SQL 스크립트)에 테스트 스크립트를 게시 하여 3 가지 유형의 LOB 할당 (데이터 크기를 기준으로 함)과 각각이 논리적 및 물리적 판독. 귀하의 경우, XML 데이터가 실제로 행당 42,000 바이트 미만인 경우, 그 중 어느 것도 가장 효율적인 TEXT_TREE 구조에 있지 않아야합니다.
SQL Server가 해당 클러스터형 인덱스를 얼마나 빨리 검색 할 수 있는지 테스트하려면 해당 XML 열을 포함 하지 않는SELECT TOP 1000
하나 이상의 열을 지정하십시오 . 결과에 어떤 영향이 있습니까? 꽤 빠릅니다.
그러한 테이블 구조 / 데이터 패턴을 갖는 것이 합리적인 것으로 간주됩니까?
실제 테이블 구조 및 데이터 패턴에 대한 설명이 불완전한 경우 누락 된 세부 사항이 무엇인지에 따라 어떤 답변도 최적이 아닐 수 있습니다. 이를 염두에두고 테이블 구조 또는 데이터 패턴에 대해 분명히 불합리한 것은 없다고 말하고 싶습니다.
(ac # 앱에서) XML을 20KB에서 ~ 2.5KB로 압축하여 VARBINARY 열에 저장하여 LOB 데이터 페이지의 사용을 방지 할 수 있습니다. 내 테스트에서 SELECT의 속도가 20 배 빨라집니다.
모든 열을 선택하거나 심지어 XML 데이터 (현재 VARBINARY
) 만 더 빨리 선택했지만 실제로는 "XML"데이터를 선택하지 않는 쿼리가 손상됩니다. 다른 열에 약 50 바이트가 있고 FILLFACTOR
100 이 있다고 가정하면 다음을 수행하십시오.
압축 없음 : 15k의 XML
데이터에는 2 개의 LOB_DATA 페이지가 필요하며 인라인 루트에 대한 2 개의 포인터가 필요합니다. 첫 번째 포인터는 24 바이트이고 두 번째 포인터는 12이며, XML 데이터의 경우 행에 총 36 바이트가 저장됩니다. 총 행 크기는 86 바이트이며 이러한 행 중 약 93 개를 8060 바이트 데이터 페이지에 맞출 수 있습니다. 따라서 백만 개의 행에는 10,753 개의 데이터 페이지가 필요합니다.
사용자 지정 압축 : 2.5k의 VARBINARY
데이터가 줄에 맞습니다. 총 행 크기는 2610 (2.5 * 1024 = 2560) 바이트이며 이러한 행 중 3 개만 8060 바이트 데이터 페이지에 맞출 수 있습니다. 따라서 백만 개의 행에는 333,334 개의 데이터 페이지가 필요합니다.
Ergo, 사용자 지정 압축을 구현 하면 클러스터형 인덱스의 데이터 페이지 가 30 배 증가 합니다. 즉, 클러스터형 인덱스 스캔을 사용하는 모든 쿼리에는 읽을 데이터 페이지 가 약 322,500 개 더 있습니다. 이러한 유형의 압축 수행에 대한 추가 효과는 아래의 세부 섹션을 참조하십시오.
의 성능을 기반으로 리팩토링을 수행하지 않도록주의해야합니다 SELECT TOP 1000 *
. 이는 애플리케이션이 발행 할 쿼리 일 가능성이 높으며, 잠재적으로 불필요한 최적화를위한 유일한 기초로 사용되어서는 안됩니다.
보다 자세한 정보와 추가 테스트를 보려면 아래 섹션을 참조하십시오.
이 질문에 대한 명확한 답을 제시 할 수는 없지만, 우리는 적어도 정확한 진전을 찾아내는 데 도움이되도록 최소한의 진전을 이루고 추가 연구를 제안 할 수 있습니다 (이상적으로 근거에 근거 함).
우리가 아는 것 :
- 테이블에는 약 백만 개의 행이 있습니다
- 테이블 크기는 약 15GB
- 표가 하나 포함
XML
열 및 유형의 여러 가지 다른 열을 : INT
, BIGINT
, UNIQUEIDENTIFIER
, "등"
XML
열 "size"는 평균 약 15k입니다.
- 를 실행 한 후
DBCC DROPCLEANBUFFERS
다음 쿼리를 완료하는 데 20-25 초가 걸립니다.SELECT TOP 1000 * FROM TABLE
- 클러스터형 인덱스를 스캔하는 중
- 클러스터형 인덱스의 조각화는 0 %에 가깝습니다.
우리가 생각하는 것 :
- 이 쿼리 이외의 다른 디스크 활동은 없습니다. 확실합니까? 다른 사용자 쿼리가 없더라도 백그라운드 작업이 수행됩니까? IO 중 일부를 차지할 수있는 동일한 시스템에서 SQL Server 외부에서 실행중인 프로세스가 있습니까? 없을 수도 있지만 제공된 정보만으로는 명확하지 않습니다.
- 15MB의 XML 데이터가 리턴되고 있습니다. 이 숫자는 무엇을 기준으로합니까? 1000 행에 행당 평균 15k의 XML 데이터를 곱한 추정치? 또는 해당 쿼리에 대해 수신 된 프로그램의 집계입니까? 그것이 단지 추정이라면, XML 데이터의 분포가 단순한 평균으로 암시되는 방식이 아닐 수도 있기 때문에 나는 그것에 의존하지 않을 것입니다.
XML 압축이 도움이 될 수 있습니다. .NET에서 압축을 정확히 어떻게 수행 하시겠습니까? 비아 GZipStream 또는 DeflateStream 클래스? 이것은 비용이 들지 않는 옵션이 아닙니다. 확실히 일부 데이터를 큰 비율로 압축하지만 매번 데이터를 압축 / 압축 해제하는 추가 프로세스가 필요하므로 더 많은 CPU가 필요합니다. 이 계획은 또한 다음과 같은 능력을 완전히 제거합니다.
- 를 통해 XML 데이터를 쿼리합니다
.nodes
..value
, .query
, 및 .modify
XML 기능을 제공합니다.
XML 데이터를 색인화하십시오.
XML
데이터 유형이 사전에 요소 및 속성 이름을 저장하고 각 항목에 정수 색인 ID를 할당 한 다음 해당 정수 ID를 사용하여 데이터 유형이 이미 최적화되어 있음 을 명심하십시오 (XML은 "중복 중복" 이므로) 문서 전체에서 (따라서 각 사용마다 전체 이름을 반복하지 않으며 요소의 닫는 태그로 다시 반복하지 않습니다). 실제 데이터에도 불필요한 공백이 제거되었습니다. 이것이 추출 된 XML 문서가 원래 구조를 유지하지 않는 이유와 빈 요소 <element />
가<element></element>
. 따라서 GZip (또는 다른 것)을 통한 압축으로 얻는 이득은 요소 및 / 또는 속성 값을 압축하여 만 찾을 수 있습니다. 바로 위에 언급 된 기능.
또한 XML 데이터를 압축하고 VARBINARY(MAX)
결과를 저장 한다고해서 LOB 액세스가 제거되는 것이 아니라 감소시킬뿐입니다. 행에있는 나머지 데이터의 크기에 따라 압축 된 값이 행에 맞거나 LOB 페이지가 여전히 필요할 수 있습니다.
이 정보는 도움이 되더라도 충분하지 않습니다. 쿼리 성능에 영향을 미치는 많은 요소가 있으므로 진행 상황에 대한보다 자세한 그림이 필요합니다.
우리가 모르는 것, 그러나 :
- 왜 성능이
SELECT *
중요합니까? 코드에서 사용하는 패턴입니까? 그렇다면 왜 그렇습니까?
- XML 열만 선택하는 성능은 무엇입니까? 다음과 같은 경우 통계 및 타이밍은 무엇입니까
SELECT TOP 1000 XmlColumn FROM TABLE;
?
이 1000 개의 행을 반환하는 데 걸리는 20 ~ 25 초의 시간은 네트워크 요소 (전세계에서 데이터 가져 오기)와 관련이 있으며 클라이언트 요소와 관련된 정도 (약 15MB + SSMS의 그리드에 XML 데이터가 있거나 디스크에 저장할 수 있습니까?
작업의이 두 가지 측면을 제외하면 단순히 데이터를 반환하지 않고 수행 할 수 있습니다. 이제 임시 테이블 또는 테이블 변수를 선택하려고 생각할 수도 있지만 몇 가지 새로운 변수 (예 : 디스크 I / O tempdb
, 트랜잭션 로그 쓰기, tempdb 데이터 및 / 또는 로그 파일의 자동 증가)가 필요합니다. 버퍼 풀의 공간 등). 이러한 모든 새로운 요소는 실제로 쿼리 시간을 늘릴 수 있습니다. 대신, 나는 일반적으로 열을 SQL_VARIANT
각각의 새로운 행 (즉 SELECT @Column1 = tab.Column1,...
)으로 덮어 쓰는 변수 (적절한 데이터 유형; 아닌 )에 저장합니다 .
그러나이 DBA.StackExchange Q & A에서 @PaulWhite가 지적한 것처럼 Logical은 동일한 LOB 데이터에 액세스 할 때 PasteBin에 게시 된 자체 연구에 대한 추가 연구와 함께 다른 읽기를 읽습니다 (LOB 읽기에 대한 다양한 시나리오를 테스트하는 T-SQL 스크립트 ) , LOB를 일관 간의 액세스되지 않는 SELECT
, SELECT INTO
, SELECT @XmlVariable = XmlColumn
, SELECT @XmlVariable = XmlColumn.query(N'/')
,와 SELECT @NVarCharVariable = CONVERT(NVARCHAR(MAX), XmlColumn)
. 따라서 우리의 옵션은 여기에서 조금 더 제한적이지만 여기에 수행 할 수있는 작업이 있습니다.
- SSMS 또는 SQLCMD.EXE에서 SQL Server를 실행하는 서버에서 쿼리를 실행하여 네트워크 문제를 배제하십시오.
- 쿼리 옵션-> 결과-> 그리드로 이동하여 "실행 후 결과 폐기"옵션을 확인하여 SSMS에서 클라이언트 문제를 배제하십시오. 이 옵션은 메시지를 포함한 모든 출력을 방지하지만 SSMS가 각 행당 메모리를 할당 한 다음 그리드에 그리는 데 걸리는 시간을 배제하는 데 여전히 유용 할 수 있습니다.
또는 SQLCMD.EXE를 통해 쿼리를 실행하고 다음을 통해 출력이 아무데도 가지 않도록 지시 할 수 -o NUL:
있습니다.
- 이 쿼리와 관련된 대기 유형이 있습니까? 그렇다면 대기 유형은 무엇입니까?
뭐라고입니다 실제 에 대한 데이터 크기 XML
열을 반환 ? 전체 테이블에서 해당 열의 평균 크기는 "TOP 1000"행에 전체 XML
데이터 의 상당 부분이 포함되어 있으면 실제로 중요하지 않습니다 . TOP 1000 행에 대해 알고 싶다면 해당 행을보십시오. 다음을 실행하십시오 :
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;
- 정확한 테이블 스키마. 모든 색인을 포함 하여 전체
CREATE TABLE
설명을 제공하십시오 .
- 쿼리 계획? 게시 할 수있는 것입니까? 그 정보는 아마도 아무것도 바꾸지 않을 것이지만 그것이 잘못되지 않을 것이라고 추측하는 것보다 그렇지 않다는 것을 아는 것이 더 좋습니다 ;-)
- 데이터 파일에 물리적 / 외부 조각화가 있습니까? 이것이 중요한 요인은 아니지만 SSD 또는 초고가 SATA가 아닌 "소비자 급 SATA"를 사용하고 있기 때문에 하위 섹터 순서의 영향은 특히 해당 섹터 수에 따라 더욱 두드러집니다 읽을 필요가 증가합니다.
다음 쿼리 의 정확한 결과는 무엇입니까?
SELECT * FROM sys.dm_db_index_physical_stats(DB_ID(),
OBJECT_ID(N'dbo.SchemaName.TableName'), 1, 0, N'LIMITED');
최신 정보
비슷한 상황이 발생하는지 확인하기 위해이 시나리오를 재현해야합니다. 그래서 몇 개의 열 (질문의 모호한 설명과 유사)이있는 테이블을 만든 다음 백만 개의 행으로 채우고 XML 열에는 행 당 약 15k의 데이터가 있습니다 (아래 코드 참조).
내가 찾은 것은 SELECT TOP 1000 * FROM TABLE
처음 8 초 만에 완료 하고 그 이후에 2-4 초마다 완료하는 것입니다 (예, DBCC DROPCLEANBUFFERS
각 SELECT *
쿼리 실행 전에 실행 ). 그리고 몇 년 전의 랩톱은 SQL Server 2012 SP2 Developer Edition, 64 비트, 6GB RAM, 이중 2.5Ghz Core i5 및 5400RPM SATA 드라이브로 빠르지 않습니다 . SSMS 2014, SQL Server Express 2014, Chrome 및 기타 여러 가지도 실행 중입니다.
시스템의 응답 시간을 기준으로 20-25 초의 응답 시간의 원인을 좁히기 위해 더 많은 정보 (예 : 테이블 및 데이터, 제안 된 테스트 결과 등)가 필요하다는 점을 반복합니다. 당신이보고있는.
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.
*/
그리고 비 LOB 페이지를 읽는 데 걸리는 시간을 고려하고 싶기 때문에 다음 쿼리를 실행하여 XML 열을 제외한 모든 열을 선택했습니다 (위에서 제안한 테스트 중 하나). 이것은 1.5 초 안에 상당히 일관되게 돌아옵니다.
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.
*/
결론 (현재)
시나리오를 재현하려는 시도를 바탕으로 SATA 드라이브 또는 비 순차 I / O를 20-25 초의 주요 원인으로 지적 할 수 있다고 생각하지 않습니다. XML 열을 포함하지 않을 때 쿼리가 얼마나 빨리 반환되는지 모릅니다. 그리고 나는 당신이 보여주는 많은 수의 논리 읽기 (비 LOB)를 재현 할 수 없었지만 , 그 내용 과 진술 에 비추어 각 행에 더 많은 데이터를 추가해야한다고 생각합니다 .
테이블 페이지의 ~ 90 %가 LOB_DATA입니다.
내 테이블에는 백만 개의 행이 있으며 각 행에는 15k 이상의 XML 데이터 sys.dm_db_index_physical_stats
가 있으며 LOB_DATA 페이지는 2 백만입니다. 나머지 10 %는 222k IN_ROW 데이터 페이지이지만 11,630 개만 있습니다. 다시 한 번, 실제 테이블 스키마 및 실제 데이터에 대한 추가 정보가 필요합니다.