SQL Server nvarchar (max) vs nvarchar (n) wirkt sich auf die Leistung aus

16

Dies ist SQL Server 2008 R2 SP2. Ich habe 2 Tische. Beide sind identisch (Daten und Indexierung), mit der Ausnahme, dass die erste Tabelle die Spalte VALUE nvarchar(max)und die zweite die gleiche Spalte hat nvarchar(800). Diese Spalte ist in einem nicht gruppierten Index enthalten. Ich habe auch einen Clustered-Index für beide Tabellen erstellt. Ich habe auch die Indizes neu aufgebaut. Die maximale Stringlänge in dieser Spalte beträgt 650.

Wenn ich dieselbe Abfrage für beide ausführe, ist die nvarchar(800)Tabelle durchgehend schneller, vielfach doppelt so schnell. Sicher scheint es den Zweck von "varchar" zu besiegen. Die Tabelle enthält mehr als 800.000 Zeilen. Die Abfrage sollte ungefähr 110.000 Zeilen umfassen (nach Schätzungen des Plans).

Laut den io-Statistiken gibt es keine Lob-Reads, so dass alles in Reihe zu sein scheint. Die Ausführungspläne sind identisch, mit der Ausnahme, dass sich die Kosten in Prozent zwischen den beiden Tabellen geringfügig unterscheiden und die geschätzte Zeilengröße mit nvarchar(max)91 Bytes gegenüber 63 Bytes größer ist . Die Anzahl der Lesevorgänge ist ebenfalls ziemlich gleich.

Warum der Unterschied?

===== Schema ======

 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]

===== Abfrage Tabelle1 ======

    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-Abfrage ======

    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)
Brian Bohl
quelle
4
Abfragen, Tabellenschema, Beispiel- oder Richtdaten und die Ausführungspläne für jede Abfrage bitte. "Ich glaube nicht ..." ist nicht dasselbe wie "Es gibt definitiv keine ...".
Mark Storey-Smith
Welche Version von SQL Server haben Sie?
Max Vernon
Weitere Informationen zum In-Row-Speicher für nvarchar (max) -Felder finden Sie unter technet.microsoft.com/en-us/library/ms189087(v=SQL.105).aspx . Wie groß sind die tatsächlichen Daten in diesen Feldern?
Max Vernon
Ich habe den Beitrag aktualisiert, um das obige Feedback zu berücksichtigen.
Brian Bohl

Antworten:

14

Sie sehen den Kostenaufwand für die Verwendung von MAXTypen.

Obwohl dies NVARCHAR(MAX)mit NVARCHAR(n)TSQL identisch ist und in Reihe gespeichert werden kann, wird es von der Speicher-Engine separat behandelt, da es in Reihe verschoben werden kann. Im Off-Row-Modus handelt es sich eher um eine LOB_DATAZuordnungseinheit als um eine ROW_OVERFLOW_DATAZuordnungseinheit, und wir können anhand Ihrer Beobachtungen davon ausgehen, dass dies mit einem Mehraufwand verbunden ist.

Sie können sehen, dass die beiden Typen intern unterschiedlich gespeichert sind, mit ein wenig DBCC PAGE-Spelunking . Mark Rasmussen hat Beispiel-Seitenabbilder gepostet, die die Unterschiede in Wie groß ist der LOB-Zeiger für (MAX) -Typen wie Varchar, Varbinary, usw.?

Wir können wahrscheinlich annehmen, dass es GROUP BYdie MAXSpalte ist, die den Leistungsunterschied in Ihrem Fall verursacht. Ich habe keine anderen Operationen an einem MAXTyp getestet, aber es könnte interessant sein, dies zu tun und zu prüfen, ob ähnliche Ergebnisse erzielt werden.

Mark Storey-Smith
quelle
Sie sagen also, es gibt eine zusätzliche Verarbeitung für das Lesen von [BLOB Inline Data] im Vergleich zu einem einfachen varchar? Ich hatte einen erheblichen Overhead erwartet, wenn es nicht mehr in Reihe ging, aber alle diese Daten sind inline (verwendet dbcc ind). Und warum bringt die Gruppe von dies heraus?
Brian Bohl
Ein wenig Aufwand zum Lesen, viel für jede Berechnung darauf, z GROUP BY. @RemusRusanu könnte wahrscheinlich einen Einblick bieten (er wird hoffentlich den Ping sehen).
Mark Storey-Smith
Ich habe einen anderen Artikel gefunden , der das gleiche Verhalten dokumentiert, auch auf Augenhöhe. Ich frage mich, ob nvarchar (max) einen weniger effizienten Algorithmus verwendet.
Brian Bohl