SQL Server nvarchar (max) 및 nvarchar (n)이 성능에 영향을 미침


16

SQL Server 2008 R2 SP2입니다. 2 개의 테이블이 있습니다. 첫 번째 테이블의 값이 VALUE nvarchar(max)이고 두 번째 테이블의 값이 같은 것을 제외하고는 둘 다 동일합니다 (데이터 및 인덱싱) nvarchar(800). 이 열은 비 클러스터형 인덱스에 포함됩니다. 또한 두 테이블 모두에 클러스터형 인덱스를 만들었습니다. 또한 인덱스를 다시 작성했습니다. 이 열의 최대 문자열 길이는 650입니다.

nvarchar(800)테이블 에 대해 동일한 쿼리를 실행하면 일관되게 더 빠르며 두 배 더 빠릅니다. "varchar"의 목적을 어기는 것 같습니다. 테이블에는 800,000 개 이상의 행이 있습니다. 쿼리는 약 110,000 개의 행 (계획에서 추정 한 것)을보고 있어야합니다.

io 통계에 따르면 로브 읽기가 없으므로 모든 것이 행으로 보입니다. 실행 계획은 동일하지만 두 테이블간에 비용 비율에 약간의 차이가 있고 예상 행 크기가 nvarchar(max)(91 바이트 대 63 바이트)로 더 큽니다 . 읽기 횟수도 거의 동일합니다.

왜 차이점이 있습니까?

===== 스키마 ======

 CREATE TABLE [dbo].[table1](
        [ID] [bigint] IDENTITY(1,1) NOT NULL,
        [ProductID] [bigint] NOT NULL,
        [ProductSkeletonID] [bigint] NOT NULL,
        [Value] [nvarchar](max) NOT NULL,
        [IsKeywordSearchable] [bit] NULL,
        [ValueInteger] [bigint] NULL,
        [ValueDecimal] [decimal](18, 2) NULL,
        [ValueDate] [datetime] NULL,
        [TypeOfData] [nvarchar](20) NOT NULL,
     CONSTRAINT [PK_table1] 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]

    CREATE NONCLUSTERED INDEX [IX_table1_productskeletonid] ON [dbo].[table1] 
    (
        [ProductSkeletonID] ASC
    )
    INCLUDE ( [ProductID],
    [Value]) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

    CREATE TABLE [dbo].[table2](
        [ID] [bigint] IDENTITY(1,1) NOT NULL,
        [ProductID] [bigint] NOT NULL,
        [ProductSkeletonID] [bigint] NOT NULL,
        [Value] [nvarchar](800) NOT NULL,
        [IsKeywordSearchable] [bit] NULL,
        [ValueInteger] [bigint] NULL,
        [ValueDecimal] [decimal](18, 2) NULL,
        [ValueDate] [datetime] NULL,
        [TypeOfData] [nvarchar](20) NOT NULL,
     CONSTRAINT [PK_table2] 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]

    CREATE NONCLUSTERED INDEX [IX_table2_productskeletonid] ON [dbo].[table2] 
    (
        [ProductSkeletonID] ASC
    )
    INCLUDE ( [ProductID],
    [Value]) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]


CREATE TABLE [dbo].[table_results](
    [SearchID] [bigint] NOT NULL,
    [RowNbr] [int] NOT NULL,
    [ProductID] [bigint] NOT NULL,
    [PermissionList] [varchar](250) NULL,
    [SearchWeight] [int] NULL,
 CONSTRAINT [PK_table_results] PRIMARY KEY NONCLUSTERED 
(
    [SearchID] ASC,
    [RowNbr] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE NONCLUSTERED INDEX [IX_table_results_SearchID] ON [dbo].[cart_product_searches_results] 
(
    [SearchID] ASC
)
INCLUDE ( [ProductID]) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

===== Table1 쿼리 ======

    SELECT cppev.ProductSkeletonID, cppev.Value, COUNT(*) AS Value FROM table1 cppev
    JOIN search_results cpsr ON cppev.ProductID = cpsr.ProductID AND cpsr.SearchID = 227568 
    WHERE cppev.ProductSkeletonID in (3191, 3160, 3158, 3201)
    GROUP BY cppev.ProductSkeletonID, cppev.Value

    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'table1'. Scan count 4, logical reads 582, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'table_results'. Scan count 1, logical reads 82, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    SQL Server Execution Times:
       CPU time = 1373 ms,  elapsed time = 1576 ms.

 |--Compute Scalar(DEFINE:([Expr1005]=CONVERT_IMPLICIT(int,[Expr1008],0)))
       |--Stream Aggregate(GROUP BY:([cppev].[Value], [cppev].[ProductSkeletonID]) DEFINE:([Expr1008]=Count(*)))
            |--Sort(ORDER BY:([cppev].[Value] ASC, [cppev].[ProductSkeletonID] ASC))
                 |--Hash Match(Inner Join, HASH:([cpsr].[ProductID])=([cppev].[ProductID]), RESIDUAL:([dbo].[table1].[ProductID] as [cppev].[ProductID]=[dbo].[table_results].[ProductID] as [cpsr].[ProductID]))
                      |--Index Seek(OBJECT:([dbo].[table_results].[IX_table_results_SearchID] AS [cpsr]), SEEK:([cpsr].[SearchID]=(227568)) ORDERED FORWARD)
                      |--Index Seek(OBJECT:([dbo].[table1].[IX_table1_productskeletonid] AS [cppev]), SEEK:([cppev].[ProductSkeletonID]=(3158) OR [cppev].[ProductSkeletonID]=(3160) OR [cppev].[ProductSkeletonID]=(3191) OR [cppev].[ProductSkeletonID]=(3201)) ORDERED FORWARD)

===== Table2 쿼리 ======

    SELECT cppev.ProductSkeletonID, cppev.Value, COUNT(*) AS Value FROM table2 cppev
    JOIN table_results cpsr ON cppev.ProductID = cpsr.ProductID AND cpsr.SearchID = 227568 
    WHERE cppev.ProductSkeletonID in (3191, 3160, 3158, 3201)
    GROUP BY cppev.ProductSkeletonID, cppev.Value

    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'table2'. Scan count 4, logical reads 584, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'table_results'. Scan count 1, logical reads 82, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    SQL Server Execution Times:
       CPU time = 484 ms,  elapsed time = 796 ms.

  |--Compute Scalar(DEFINE:([Expr1005]=CONVERT_IMPLICIT(int,[Expr1008],0)))
       |--Stream Aggregate(GROUP BY:([cppev].[Value], [cppev].[ProductSkeletonID]) DEFINE:([Expr1008]=Count(*)))
            |--Sort(ORDER BY:([cppev].[Value] ASC, [cppev].[ProductSkeletonID] ASC))
                 |--Hash Match(Inner Join, HASH:([cpsr].[ProductID])=([cppev].[ProductID]), RESIDUAL:([auctori_core_v40_D].[dbo].[table2].[ProductID] as [cppev].[ProductID]= [dbo].[table2].[ProductID] as [cpsr].[ProductID]))
                      |--Index Seek(OBJECT:([dbo].[table_results].[IX_table_results_SearchID] AS [cpsr]), SEEK:([cpsr].[SearchID]=(227568)) ORDERED FORWARD)
                      |--Index Seek(OBJECT:([dbo].[table2].[IX_table2_productskeletonid] AS [cppev]), SEEK:([cppev].[ProductSkeletonID]=(3158) OR [cppev].[ProductSkeletonID]=(3160) OR [cppev].[ProductSkeletonID]=(3191) OR [cppev].[ProductSkeletonID]=(3201)) ORDERED FORWARD)

4
쿼리, 테이블 스키마, 샘플 또는 표시 데이터 및 각 쿼리에 대한 실행 계획을 확인하십시오. "생각하지 않는다 ..."는 "확실하지 않다 ..."와는 다릅니다.
Mark Storey-Smith

어떤 버전의 SQL Server가 있습니까?
Max Vernon

nvarchar (max) 필드의 행 내 저장에 대한 자세한 내용은 technet.microsoft.com/en-us/library/ms189087(v=SQL.105).aspx 를 참조 하십시오 . 해당 필드의 실제 데이터는 얼마나됩니까?
Max Vernon

위의 피드백을 해결하기 위해 게시물을 업데이트했습니다.
Brian Bohl

답변:


14

MAX유형 을 사용하는 데 따른 비용 오버 헤드가 나타납니다 .

하지만 NVARCHAR(MAX)동일 NVARCHAR(n)TSQL과의 열 저장 될 수가 오프 행 푸시 될 수 있기 때문에, 그것은 스토리지 엔진에 의해 개별적으로 처리된다. 행 LOB_DATA외일 경우 ROW_OVERFLOW_DATA할당 단위 가 아닌 할당 단위이며 오버 헤드가 발생하는 것으로 관찰 할 수 있습니다.

약간의 DBCC PAGE spelunking으로 두 유형이 내부적으로 다르게 저장되어 있음을 알 수 있습니다 . Mark RasmussenVarchar, Varbinary 등과 같은 (MAX) 유형에 대한 LOB 포인터의 크기는 얼마입니까? 의 차이점을 보여주는 예제 페이지 덤프를 게시했습니다 .

우리는 아마이있어 가정 할 수 있습니다 GROUP BYMAX귀하의 경우 성능 차이가 발생 열입니다. MAX유형 에 대한 다른 작업을 테스트 하지는 않았지만 비슷한 결과가 나타나는지 흥미로울 수 있습니다.


따라서 [BLOB Inline Data] 대 일반 'ol varchar를 읽는 추가 처리가 있습니까? 행에서 벗어난 경우 상당한 오버 헤드가 예상되었지만이 모든 데이터는 인라인입니다 (dbcc ind 사용). 그리고 왜 그룹이 이것을 이끌어 내고 있다고 생각합니까?
Brian Bohl

그것을 읽는 데 약간의 오버 헤드가 GROUP BY있습니다. @RemusRusanu는 약간의 통찰력을 제공 할 수 있습니다 (Ping을 기대할 것입니다).
Mark Storey-Smith

나는 같은 행동을 똑같이 묘사하는 다른 기사 를 발견 했다. nvarchar (max)가 덜 효율적인 알고리즘을 사용하는지 궁금합니다.
Brian Bohl
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.