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)
quelle
Antworten:
Sie sehen den Kostenaufwand für die Verwendung von
MAX
Typen.Obwohl dies
NVARCHAR(MAX)
mitNVARCHAR(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 eineLOB_DATA
Zuordnungseinheit als um eineROW_OVERFLOW_DATA
Zuordnungseinheit, 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 BY
dieMAX
Spalte ist, die den Leistungsunterschied in Ihrem Fall verursacht. Ich habe keine anderen Operationen an einemMAX
Typ getestet, aber es könnte interessant sein, dies zu tun und zu prüfen, ob ähnliche Ergebnisse erzielt werden.quelle
GROUP BY
. @RemusRusanu könnte wahrscheinlich einen Einblick bieten (er wird hoffentlich den Ping sehen).