Что нового в SQL Server 2019: более быстрые переменные таблицы (и новые проблемы с параметром Sniffing)

Tags: SQL Server 2019, таблица

Более десяти лет обработка табличных переменных SQL была плохой. Чтобы продемонстрировать, насколько ужасная оценка мощности, используем этот запрос Stack Overflow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
declare @VoteStats table (PostId int, up int, down int)
 
insert @VoteStats
select
    PostId,
    up = sum(case when VoteTypeId = 2 then 1 else 0 end),
    down = sum(case when VoteTypeId = 3 then 1 else 0 end)
from Votes
where VoteTypeId in (2,3)
group by PostId
 
select top 100 p.Id as [Post Link] , up, down
from @VoteStats
join Posts p on PostId = p.Id
where down > (up * 0.5) and p.CommunityOwnedDate is null and p.ClosedDate is null
order by up desc
GO

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

 

 

 

Посмотрите, как верхний запрос имеет стоимость относительно партии 100%, а второй запрос говорит 0%? Да, это порочная ложь: она основана на приблизительной стоимости запросов, даже если вы смотрите на фактический план. Коренной причиной является количество строк, которые SQL Server ожидает найти в переменной таблицы во втором запросе - наведите указатель мыши на это:

 

 

 

Оценочное количество строк только одностороннее, потому что мы только что вставили в него 2,7 млн. строк. В результате SQL Server говорит: «Я просто получу одну строку из переменной таблицы, сделайте одно соответствие индексу в таблице Posts, а затем мне не нужно будет сортировать эти данные в плане».

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

Теперь давайте попробуем это в SQL Server 2019.

Тот же запрос, никаких изменений, просто запуск его в базе данных с уровнем совместимости 150:

  • Продолжительность: раньше было 57 секунд, теперь 7 секунд с 2019
  • Логические чтения: раньше было ~ 1 млн, теперь около 70 тыс. с 2019

Вот план запросов SQL Server 2019:

 

 

Теперь, по оценкам SQL Server 2019, из таблицы будет выходить 800 тыс. строк, которые все еще не совсем точны, но, по крайней мере, намного точнее, чем оценка 1 строки. В результате он решает сортировать содержимое переменных таблицы, чтобы найти первые 100 строк FIRST перед выполнением запросов в таблице Posts.

Внезапно 2019 дает лучшие оценки переменных таблицы из коробки.

Теперь у нас есть новая проблема: параметр sniffing.

Давайте создадим индекс для Users.Location, чтобы помочь SQL Server понять, сколько строк вернется, если мы фильтруем для данного местоположения. Затем мы  создадим хранимую процедуру с двумя запросами:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

CREATE INDEX IX_Location ON dbo.Users(Location);

GO

CREATE OR ALTER PROC dbo.usp_TableVariableTest @Location NVARCHAR(40) AS

BEGIN

DECLARE @NotATable TABLE (ID INT);

INSERT INTO @NotATable

 SELECT Id

 FROM dbo.Users

 WHERE Location = @Location;

SELECT u.*

 FROM @NotATable t

 INNER JOIN dbo.Users u ON t.ID = u.Id

 ORDER BY u.DisplayName, u.Location, u.WebsiteUrl;

END

GO


Первый запрос загружает переменную таблицы со всеми пользователями в местоположении.

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

Давайте освободим кэш плана, а затем позвоним ему для действительно большого местоположения:

Здесь мы навели указатель мыши на проверку переменных таблицы, чтобы показать приблизительное и фактическое количество строк - они абсолютно идеальны! Это круто! Это означает, что SQL Server выделяет достаточно памяти для сортировки этих 1902 строк.

Теперь давайте запустим его для очень небольшой локации:

Так же, как и каждый параметр sniffing issue с ​​самого начала, SQL Server кэшировал план выполнения для первого набора параметров, которые были отправлены. Теперь он оценивает 1,902 строки - из Индии - каждый раз, когда выполняется запрос. Здесь это не сложно, когда переоценка памяти для этого запроса невелика, но теперь давайте попробуем ее в обратном порядке.

Освободите кэш плана, сначала запустите его для My Backyard, а SQL Server 2019 кэширует план выполнения для оценки с 1 строкой:

А затем запустите его для Индии, и готово: сортировка выплескивается на диск:

Индия повторно использует план оценки в 1 ряд

В большинстве случаев обработка табличной переменной SQL Server 2019 будет быстрее.

Раньше переменные таблицы обычно производили плохие планы ВСЕГО времени. Это было черно-белое. Производительность была ни о чем.

Начиная с SQL Server 2019, у нас больше шансов получить хорошие планы, по крайней мере, во многих случаях, если не в большинстве. У нас просто есть новая проблема - параметр sniffing - но вы можете научиться исправлять это довольно легко. Это очень хороший компромисс, не так ли?

No Comments

Add a Comment