SQL Server nvarchar(max)vs nvarchar(n)影响性能


16

这是SQL Server 2008 R2 SP2。我有2张桌子。两者是相同的(数据和索引),除了第一个表的VALUE列nvarchar(max)与第二个表的列相同nvarchar(800)。该列包含在非聚集索引中。我还在两个表上创建了聚集索引。我还重建了索引。此列中的最大字符串长度是650。

如果我对两个nvarchar(800)表运行相同的查询的速度始终更快,那么很多倍。当然,这似乎正在破坏“ varchar”的目的。表格包含800,000多行。该查询应查看大约110,000行(这是该计划的估计值)。

根据io统计信息,没有lob读取,因此所有内容似乎都在行中。执行计划是相同的,除了两个表之间的开销百分比略有不同,并且估计的行大小更大时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/zh-cn/library/ms189087(v=SQL.105).aspx。这些字段中的实际数据有多少?
Max Vernon

我更新了帖子以解决上述反馈。
布莱恩·布尔

Answers:


14

您会看到使用MAX类型的成本开销。

尽管NVARCHAR(MAX)NVARCHAR(n)TSQL 相同,并且可以在行中存储,但是由于可以将其下推,因此由存储引擎单独处理。当行下时,它是LOB_DATA分配单位,而不是ROW_OVERFLOW_DATA分配单位,并且从您的观察中我们可以假定这会带来开销。

您可以看到这两种类型在内部的存储方式有所不同,只需进行一点DBCC PAGE拼写马克·拉斯穆森(Mark Rasmussen)发布的示例页面转储显示了以下方面的差异(MAX)类型(如Varchar,Varbinary,Etc等)的LOB指针大小是多少?

我们可能会假设GROUP BYMAX列上的导致您的情况下的性能差异。我没有测试过某个MAX类型的其他操作,但是这样做很有趣,看看是否可以看到类似的结果。


因此,您是说要读取[BLOB内联数据]与普通的'ol varchar相比,需要进行额外的处理?我曾预计如果行结束会产生大量开销,但是所有这些数据都是内联的(使用dbcc ind)。您为什么认为这个小组提出来呢?
布莱恩·布尔

读取它的开销很小,对其进行的任何计算都很多,例如GROUP BY。@RemusRusanu可能会提供一些见识(希望他会看到Ping)。
Mark Storey-Smith

我发现另一篇文章记录了相同的行为,甚至在相等和相似时也是如此。我想知道nvarchar(max)是否使用效率较低的算法。
布莱恩·布尔
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.