To jest SQL Server 2008 R2 SP2. Mam 2 stoły. Oba są identyczne (dane i indeksowanie), z tą różnicą, że pierwsza tabela ma kolumnę WARTOŚĆ, nvarchar(max)
a druga ma taką samą kolumnę jak nvarchar(800)
. Ta kolumna jest uwzględniona w indeksie nieklastrowanym. Utworzyłem również indeks klastrowy na obu tabelach. Odbudowałem również indeksy. Maksymalna długość ciągu w tej kolumnie wynosi 650.
Jeśli uruchomię to samo zapytanie dla obu nvarchar(800)
tabel, jest to konsekwentnie szybsze, wiele razy dwa razy szybsze. Pewnie wydaje się, że pokonuje cel „varchara”. Tabela zawiera ponad 800 000 wierszy. Kwerenda powinna obejmować około 110 000 wierszy (co szacuje plan).
Według statystyk io nie ma odczytów lob, więc wszystko wydaje się być w rzędzie. Plany wykonania są takie same, z tą różnicą, że istnieje niewielka różnica w odsetku kosztów między dwiema tabelami, a szacowany rozmiar wiersza jest większy w przypadku nvarchar(max)
(91 bajtów w porównaniu z 63 bajtami). Liczba odczytów jest prawie taka sama.
Skąd ta różnica?
===== Schemat ======
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]
===== Tabela 1 kwerenda ======
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)
===== Tabela 2 ======
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)
źródło
Odpowiedzi:
Widzisz koszty ogólne związane z używaniem
MAX
typów.Chociaż
NVARCHAR(MAX)
jest identyczny jakNVARCHAR(n)
w TSQL i może być przechowywany w wierszu, jest obsługiwany osobno przez silnik pamięci, ponieważ można go wypchnąć z wiersza. Poza rzędem jest toLOB_DATA
jednostka alokacji, a nieROW_OVERFLOW_DATA
jednostka alokacji, a na podstawie twoich obserwacji możemy założyć, że pociąga to za sobą koszty ogólne.Widać, że te dwa typy są wewnętrznie przechowywane inaczej z niewielkim spelunkowaniem STRONY DBCC . Mark Rasmussen opublikował przykładowe zrzuty stron, które pokazują różnice w tym, jaki jest rozmiar wskaźnika LOB dla typów (MAX), takich jak Varchar, Varbinary, itp.?
Możemy chyba założyć, że to
GROUP BY
naMAX
kolumnie, która powoduje różnicę wydajności w Twoim przypadku. Nie testowałem innych operacji naMAX
typie, ale może to być interesujące i sprawdzić, czy podobne wyniki są widoczne.źródło
GROUP BY
. @RemusRusanu prawdopodobnie może zaoferować pewien wgląd (mam nadzieję, że zobaczy ping).