Чи кеширує значення SQL Server у запиті?


10

Кожен раз, коли я стикаюся з таким типом запитів, я завжди задаюся питанням, як би SQL Server працював над цим. Якщо я запускаю будь-який тип запиту, який вимагає обчислення, а потім використовувати це значення в декількох місцях, наприклад, в selectі order by, чи буде SQL Server обчислювати його двічі за кожну рядок чи він буде кешований? Крім того, як це працює з визначеними користувачем функціями?

Приклади:

SELECT CompanyId, Count(*)
FROM Sales
ORDER BY Count(*) desc

SELECT Geom.BufferWithTolerance(@radius, 0.01, 0).STEnvelope().STPointN(1).STX, Geom.BufferWithTolerance(@radius, 0.01, 0).STEnvelope().STPointN(1).STY
FROM Table

SELECT Id, udf.MyFunction(Id)
FROM Table
ORDER BY udf.MyFunction(Id)

Чи є спосіб зробити його більш ефективним або SQL Server досить розумний, щоб обробляти це за мене?


"це залежить" ось один експонат rextester.com/DXOB90032
Мартін Сміт

Що ви можете порівняти з rextester.com/ARSO25902
Мартін Сміт

@MartinSmith Ви не використовуєте недетерміновану функцію? Якщо це так, я б очікував, що SQL виконає його двічі.
Йонас Ставський

завжди є виняток! Ви можете спробувати SELECT RAND() FROM Sales order by RAND()- це оцінюється лише один раз, оскільки це не детерміновано, а константа часу виконання.
Мартін Сміт

Відповіді:


11

Оптимізатор запитів SQL Server може поєднувати повторені обчислені значення в одному операторі Compute Scalar. Чи буде це робити чи ні, залежить від калькуляції плану запиту та властивостей розрахункової вартості. Як і очікувалося, це не зробить для обчислених значень, які не є детермінованими, а кілька винятків, таких як RAND(). Це також не буде робити це для визначених користувачем функцій.

Почну з прикладу функції, визначеної користувачем. Ось чудовий приклад функції, визначеної користувачем:

CREATE OR ALTER FUNCTION dbo.NULL_FUNCTION (@N BIGINT) RETURNS BIGINT
WITH SCHEMABINDING
AS
BEGIN
RETURN NULL;
END;

Я також хочу створити таблицю і помістити в неї 100 рядків:

CREATE TABLE X_100 (N BIGINT NOT NULL);

WITH
L0   AS(SELECT 1 AS c UNION ALL SELECT 1),
L1   AS(SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B),
L2   AS(SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B),
L3   AS(SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B),
L4   AS(SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B),
L5   AS(SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B),
Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n FROM L5)
INSERT INTO X_100 WITH (TABLOCK)
SELECT n
FROM Nums WHERE n <= 100;

dbo.NULL_FUNCTIONФункція determistic. Скільки разів він буде виконаний для наступного запиту?

SELECT n, dbo.NULL_FUNCTION(n)
FROM X_100;

На основі плану запитів це буде виконано один раз для кожного рядка або 100 разів:

план запиту 1

SQL Server 2016 представив DMV sys.dm_exec_function_stats . Ми можемо зробити знімки цього DMV, щоб побачити, скільки разів UDF виконується за допомогою запиту.

SELECT execution_count
FROM sys.dm_exec_function_stats
WHERE object_id = OBJECT_ID('NULL_FUNCTION');

Результат цього - 100, тому функція виконувалася 100 разів.

Спробуємо ще один простий запит:

SELECT n, dbo.NULL_FUNCTION(n), dbo.NULL_FUNCTION(n) 
FROM X_100;

План запитів передбачає, що функція буде виконуватися в 200 разів:

план запиту 2

Результати sys.dm_exec_function_statsговорять про те, що функцію виконували 200 разів.

Зауважте, що ви не завжди можете використовувати план запитів, щоб визначити, скільки разів виконується обчислювальний скаляр. Наступна цитата з " Обчислити скаляри, вирази та результати виконання плану ":

Це змушує людей думати, що Compute Scalar поводиться як більшість інших операторів: коли рядки проходять через нього, результати потоку будь-яких обчислень, які містить Compute Scalar, додаються до потоку. Це взагалі не відповідає дійсності. Незважаючи на назву, Compute Scalar не завжди що-небудь обчислює, і не завжди містить одне скалярне значення (це може бути, наприклад, вектор, псевдонім або навіть булевий предикат). Частіше за все обчислювальний скаляр просто визначає вираз; власне обчислення відкладається, поки щось не пізніше в плані виконання не потребує результату.

Спробуємо ще один приклад. Для наступного запиту я сподіваюся, що UDF обчислюється один раз:

WITH NULL_FUNCTION_CTE (NULL_VALUE) AS
(
SELECT DISTINCT dbo.NULL_FUNCTION(0)
)
SELECT n , cte.NULL_VALUE
FROM X_100
CROSS JOIN NULL_FUNCTION_CTE cte;

План запитів передбачає, що він буде обчислений одноразово:

план запитів

Однак DMV розкриває правду. Обчислювальний скаляр відкладається до тих пір, поки це не потрібно, що є в операторі з'єднання. Оцінюється 100 разів.

Ви також запитали, що ви можете зробити, щоб заохотити оптимізатор, щоб уникнути повторного перерахунку одного і того ж виразу кілька разів. Найкраще, що ви можете зробити, це уникати використання скалярних АДС у своєму коді. Поза цим питанням є ряд питань щодо продуктивності, включаючи надуття грантів пам’яті, змушування запускати весь запит MAXDOP 1, неправильні оцінки кардинальності та призводити до додаткового використання процесора. Якщо вам потрібно використовувати UDF і значення цього UDF є постійною, ви можете обчислити його поза запитом і помістити його в локальну змінну.

Для запитів без UDF можна спробувати уникнути написання виразів, які повертають один і той же результат, але не вводяться точно так само. Для цього наступного прикладу я використовую загальнодоступну базу даних AdventureworksDW2016CTP3, але дійсно будь-яка база даних буде робити. Скільки разів буде COUNT(*)обчислено цей запит?

SELECT OrderDateKey, COUNT(*) 
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;

Для цього запиту ми можемо визначити це, подивившись на оператора Hash Match (агрегат).

хеш-сірник

COUNT(*)Обчислюється один раз для кожного унікального значення OrderDateKey. Включення цього ORDER BYпункту не спричиняє його обчислення вдвічі. План виконання ви можете подивитися тут .

Тепер розглянемо запит, який дасть ті самі результати, але записується по-іншому:

SELECT OrderDateKey, SUM(1)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;

Оптимізатор запитів недостатньо розумний для їх поєднання, тому буде проведена додаткова робота:

хеш-матч 2

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.