Подтвердить что ты не робот

Комплекс LIKE Query чрезвычайно медленный снова NVARCHAR (450), по сравнению с VARCHAR (450)

В настоящее время я задаюсь вопросом о некоторых различиях в производительности VARCHAR/NVARCHAR, особенно при использовании сложных LIKE-запросов (начинающихся с _ или%).

У меня есть набор тестов на Microsoft SQL Server 2014. У меня 2 таблицы. Оба имеют поле идентификатора (идентификатор (1, 1) и поле значения (либо VARCHAR (450), либо NVARCHAR (450)). Оба имеют идентичные 1'000'000 случайно сгенерированных записей.

Таблицы называются tblVarCharNoIndex и tblNVarCharNoIndex (поэтому индексов нет. Поведение почти одинаково, если я использую индексы).

Теперь я выполняю следующие запросы, проверяя продолжительность (один раз на VARCHAR, один раз на NVARCHAR)

SELECT * FROM tblVarcharNoIndex WHERE Value LIKE '%ab%'
SELECT * FROM tblNVarcharNoIndex WHERE Value LIKE '%ab%'

Время выполнения сильно отличается. Он занимает 1540 мс в таблице VARCHAR и 8630 мс в таблице NVARCHAR, поэтому он занимает более 5 раз дольше с NVARCHAR.

Я понимаю, что NVARCHAR имеет последствия для производительности, поскольку для хранения требуется 2 байта, это имеет смысл. Но я не могу объяснить ухудшение производительности на 500%, это не имеет для меня никакого смысла.

В соответствии с запросом здесь еще несколько Данных.

Запрос для создания таблицы

CREATE TABLE [dbo].[tblVarcharNoIndex](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Value] [varchar](450) NOT NULL,
 CONSTRAINT [PK_tblVarcharNoIndex] 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 TABLE [dbo].[tblNVarcharNoIndex](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Value] [nvarchar](450) NOT NULL,
 CONSTRAINT [PK_tblNVarcharNoIndex] 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]

Запрос для создания значений

DECLARE @cnt INT = 0;
DECLARE @entries INT = 1000000 --1'000'000;
DECLARE @maxLength INT = 450;
DECLARE @minLength INT = 50;
DECLARE @value VARCHAR(450)
DECLARE @length INT

WHILE @cnt < @entries
BEGIN
    SELECT @value = ''
    SET @length = @minLength + CAST(RAND() * (@maxLength - @minLength) as INT)
    WHILE @length <> 0
    BEGIN
        SELECT @value = @value + CHAR(CAST(RAND() * 96 + 32 as INT))
        SET @length = @length - 1
    END

    INSERT INTO tblBase(Value, NValue) VALUES (@value, @value)

    SET @cnt = @cnt + 1;

END;

(значения копируются позже из tblBase)

КАК ПРОБЛЕМА

DECLARE @start DATETIME
DECLARE @end DATETIME
DECLARE @testname NVARCHAR(100) = 'INSERT FROM other table'

--VARCHAR No Index
PRINT 'starting ''' + @testname + ''' on VARCHAR (No Index)'
SET @start = GETDATE()

SELECT * FROM tblVarcharNoIndex WHERE Value LIKE '%ab%' --This takes 1540ms

SET @end = GETDATE()
PRINT '-- finished ''' + @testname + ''' on VARCHAR (No Index)'
PRINT '-- Duration ' + CAST(DATEDIFF(mcs, @start, @end) AS VARCHAR(100)) + ' microseconds'


--NVARCHAR No Index
PRINT 'starting ''' + @testname + ''' on NVARCHAR (No Index)'
SET @start = GETDATE()

SELECT * FROM tblNVarcharNoIndex WHERE Value LIKE '%ab%' --This takes 8630ms

SET @end = GETDATE()
PRINT '-- finished ''' + @testname + ''' on NVARCHAR (No Index)'
PRINT '-- Duration ' + CAST(DATEDIFF(mcs, @start, @end) AS VARCHAR(100)) + ' microseconds'

План выполнения План выполнения обоих запросов выглядит абсолютно одинаково (мне некуда загружать изображение прямо сейчас, но это очень просто):

SELECT (0%) < --- Parallelism (Собрать потоки) (3%) < --- Кластеризованный индексный сканер ON Основной ключ (97%)

4b9b3361

Ответ 1

Теория хотя и звучит. LIKE - это оператор, который сравнивает каждое значение с частью строки. Если оператор действительно правильно настроен, и если SQL Server не знал о преимуществах одной части значения над другой, тогда SQL Server обязательно должен был запустить такой алгоритм, как следующий (пример в C#):
        for (; foundValue == false && Start < (length - 2); Start += 1)
        {
            searchValue = x.Substring(Start, 2);
            if (searchValue == compareValue)
                foundValue = true;
        }

в NVARCHAR только в два раза больше символов.

Из моего собственного тестирования я отмечаю следующее:

Таблица 'tblVarcharNoIndex'. Количество сканирования 1, логическое считывание 97, физическое читает 0, чтение вперед читает 0, логическое чтение lob 0, физическое чтение lob 0, чтение с чтением lob 0.

Таблица 'tblNVarcharNoIndex'. Число сканирования 1, логическое чтение 189, физическое читает 0, чтение вперед читает 0, логическое чтение lob 0, физическое чтение lob 0, чтение с чтением lob 0.

Логическое чтение подразумевает, сколько SQL хранилось для сравнений, и мы замечаем, что это немного больше 2x. Я думаю, что ответ можно увидеть, посмотрев на план фактического выполнения и отметив, что оценочное число строк составило 56 против 73, даже если в итоге было возвращено то же количество строк.

Однако просмотр статистики клиента показывает, что вы, вероятно, заметили:

                                                        NVAR    VAR     AVERAGE
Query Profile Statistics                    
  Number of INSERT, DELETE and UPDATE statements        0       0       0.0000
  Rows affected by INSERT, DELETE, or UPDATE statements 0       0       0.0000
  Number of SELECT statements                           2       2       2.0000
  Rows returned by SELECT statements                    306     306     306.0000
  Number of transactions                                0       0       0.0000
Network Statistics                  
  Number of server roundtrips                           1       1       1.0000
  TDS packets sent from client                          1       1       1.0000
  TDS packets received from server                      45      23      34.0000
  Bytes sent from client                                146     144     145.0000
  Bytes received from server                            180799  91692   136245.5000
Time Statistics                 
  Client processing time                                286     94      190.0000
  Total execution time                                  317     156     236.5000
  Wait time on server replies                           31      62      46.5000

Обратите внимание, что пакеты TDS, полученные с сервера, были разными (помните, что оценка строк была разрозненной), что не только занимает больше байтов, но и время для обработки. Время выполнения около 2x, но время обработки составляло 3 раза.

Насколько это связано с вашим процессором и протоколами SQL Server? Вероятно, некоторые или многие из них (этот запрос выполнялся на древнем ноутбуке Lenovo Lenovo Lenovo с Windows 10, DuoCore 1,64 ГГц, 16 ГБ DDR3). Несмотря на то, что я не отвечаю на конкретные вопросы.

Тем не менее, мы можем заключить одно: Оценка строк SQL Server влияет на клиента и данные, отправленные/полученные.

Ответ 2

Невозможно дать вам специфику без дополнительных данных, начиная с плана выполнения для обоих запросов.

Некоторые общие причины:

  • Как вы заявили, во время сканирования читается в два раза больше байтов,
  • Количество загрузок страниц увеличится
  • Объем необходимой памяти увеличится, что может привести к переполнению операций на диске.
  • Объем процессора может увеличиться, что может быть ограничено на основе настроек ОС или SQL и вызовет ожидание процессора.

Ответ 3

Правила сравнения Юникода намного сложнее, чем правила ascii.

Влияние данных Unicode на производительность осложняется множеством факторов, которые включают следующее:

  • Разница между правилами сортировки Unicode и правилами сортировки, отличными от Unicode
  • Разница между сортировкой двухбайтовых и однобайтовых символов
  • Преобразование кодовой страницы между клиентом и сервером

Ссылка: https://msdn.microsoft.com/en-us/library/ms189617.aspx

Вы можете подтвердить это, изменив сортировку столбца на двоичный.

SELECT  *
FROM    #temp2
where col1 COLLATE Latin1_General_Bin2 like '%str%' 

Наконец, некоторые соображения, если вам нужно использовать NVARCHAR и хотите повысить производительность.

  • Рассмотрим сжатие данных. Когда вы сжимаете страницу/строку в SQL Server, ваши nvarchar cols автоматически сжимаются UNICODE.() https://msdn.microsoft.com/en-us/library/ee240835(v=sql.105).aspx
  • Рассмотрим изменение сортировки. Это имеет значение для сортировки/поиска. Поэтому проверьте, дает ли он желаемые результаты для разных языков/символов.

Ответ 4

Как показано в некоторых других сообщениях, наибольшее влияние на вашу производительность в этом сценарии - это правила сравнения Unicode. Вы можете решить эту проблему в отношении "LIKE-запросов сравнения", добавив в таблицу нестроговое вычисленное поле с двоичной сортировкой:

ALTER TABLE tblNVarcharNoIndex
ADD ValueColBin AS UPPER(Value) COLLATE Latin1_General_100_Bin2;

Вместо того, чтобы запрашивать ваше постоянное поле данных, вы можете теперь запросить вычисленное поле. Обратите внимание, что двоичная сортировка чувствительна к регистру, поэтому для выполнения запросов, нечувствительных к регистру, вам нужно будет преобразовать строки поиска в верхний регистр. В следующем примере показано, как будет выглядеть обновленный запрос:

DECLARE @compare  NVARCHAR(10) = N'%AB%'

SELECT   [Id]
        ,[Value] 
FROM    tblNVarcharNoIndex 
WHERE   [ValueColBin]  LIKE @compare collate Latin1_General_100_Bin2

По-прежнему будет достигнут уровень производительности, однако он должен находиться в ожидаемом диапазоне от 1,5 до 2 медленнее (теоретически, по крайней мере). Обратите внимание, что этот метод будет иметь более высокую стоимость процессора.

Ответ 5

Запрос, который использует параметр varchar, ищет индекс из-за наборов сопоставлений столбцов.

который использует параметр nvarchar, выполняет проверку индекса из-за наборов сопоставлений столбцов.

Основным правилом, которым следует следовать, является сканирование, плохо, поиск хорош.

Сканирование индексов

Когда SQL Server выполняет проверку, он загружает объект, который он хочет прочитать с диска в память, затем просматривает этот объект сверху вниз, ища нужные ему записи.

Поиск индекса

Когда SQL Server выполняет поиск, он знает, где в индексе будут данные, поэтому он загружает индекс с диска, переходит непосредственно к той части индекса, которая ему нужна, и читает, где данные, которые ему нужны концы. Это, очевидно, гораздо более эффективная операция, чем сканирование, поскольку SQL уже знает, где находятся данные, которые он ищет.

Как мне изменить план выполнения, чтобы использовать поиск вместо сканирования?

Когда SQL Server ищет ваши данные, вероятно, одна из самых больших вещей, которые заставят SQL Server переключиться с поиска на сканирование, - это когда некоторые из столбцов, которые вы ищете, не включены в индекс, который вы хотите использовать, Чаще всего SQL Server возвращается к выполнению кластерного сканирования индекса, поскольку индекс кластеров содержит все столбцы в таблице. Это одна из самых больших причин (по крайней мере, по моему мнению), что теперь у нас есть возможность ВКЛЮЧИТЬ столбцы в индексе без добавления этих столбцов в индексированные столбцы индекса. Включив дополнительные столбцы в индекс, мы увеличиваем размер индекса, но мы разрешаем SQL Server читать индекс, не возвращаясь к кластерному индексу, или к его собственной таблице, чтобы получить эти значения.

Ссылки

Информацию о специфике каждого из этих операторов в плане выполнения SQL Server см. в разделе...

https://msdn.microsoft.com/en-us/library/ms175184.aspx

https://technet.microsoft.com/en-us/library/ms190400.aspx