SQL Server nvarchar (max) проти nvarchar (n) впливає на продуктивність


16

Це SQL Server 2008 R2 SP2. У мене 2 таблиці. Обидва ідентичні (дані та індексація), за винятком того, що перша таблиця містить стовпець VALUE якnvarchar(max) а друга має той самий стовпець, що і nvarchar(800). Цей стовпець включений до некластеризованого індексу. Я також створив кластерний індекс в обох таблицях. Я також відновив індекси. Максимальна довжина рядка в цьому стовпці - 650.

Якщо я запускаю однаковий запит проти обох nvarchar(800) таблиць, постійно швидше, у багато разів вдвічі швидше. Звичайно здається, що це перемагає мету "варчара". Таблиця містить 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]

===== Запит таблиці1 ======

    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)

===== Запит на таблицю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)

4
Запити, схема таблиці, зразки чи орієнтовні дані та плани виконання кожного запиту, будь ласка. "Я не думаю ..." - це не те саме, що "Однозначно немає ...".
Марк Сторі-Сміт

Яку версію SQL Server у вас є?
Макс Вернон

Докладнішу інформацію про рядкове зберігання для полів nvarchar (max) див. У technet.microsoft.com/en-us/library/ms189087(v=SQL.105).aspx . Наскільки великі фактичні дані в цих полях?
Макс Вернон

Я оновив публікацію, щоб вирішити вищезазначені відгуки.
Брайан Бол

Відповіді:


14

Ви бачите накладні витрати на використання MAXтипів.

Хоча NVARCHAR(MAX)він ідентичний NVARCHAR(n)у TSQL і може зберігатися в рядку, він обробляється окремо за допомогою двигуна зберігання, оскільки його можна висунути в рядку. Коли це не рядок, це LOB_DATAодиниця розподілу, а неROW_OVERFLOW_DATA одиниця розподілу, і ми можемо припустити з ваших спостережень, що це несе накладні витрати.

Ви можете бачити, що два типи внутрішньо зберігаються по-різному з невеликим вимовою DBCC PAGE . Марк Расмуссен розмістив на прикладі дамп-сторінки, на яких показано відмінності в розмірах вказівника LOB для (MAX) типів, таких як Varchar, Varbinary, Etc?

Ми можемо припустити, що GROUP BYсаме MAXстовпець, що викликає різницю в продуктивності у вашому випадку. Я не перевіряв інших операцій на MAXтипі, але це може бути цікаво зробити і подивитися, чи не спостерігаються подібні результати.


Отже, ви говорите, що є додаткова обробка читання [Вбудовані дані BLOB] проти звичайного 'ol varchar? Я очікував значних накладних витрат, якщо він піде з рядка, але всі ці дані є вбудованими (використовувався dbcc ind). І чому ви вважаєте, чому група виводить це?
Брайан Бол

Невеликий наклад для його читання, багато для будь-яких обчислень на ньому, наприклад GROUP BY. @RemusRusanu, ймовірно, може запропонувати деяку інформацію (він, сподіваємось, побачить пінг).
Марк Сторі-Сміт

Я знайшов ще одну статтю, яка документує таку саму поведінку, навіть на рівних і подібних. Цікаво, чи використовує nvarchar (max) менш ефективний алгоритм.
Брайан Бол
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.