#sql #sql-server #performance #tsql
#sql #sql-server #Производительность #tsql
Вопрос:
Использование SQL Server 2008 R2
У меня есть 20-секундный запрос от этой функции, которая использует курсор, цикл While и вызывает другую функцию.
Исходя из показанного кода — каким был бы наилучший способ повысить производительность этого узкого места?
USE [DB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[ufnGetForUser] (@UserName VARCHAR(100))
RETURNS @User TABLE (
ID INT
,Name VARCHAR(40)
,ParentID INT
,CompanyID INT
,CompanyName VARCHAR(100)
,CompanyDisplayName VARCHAR(100)
,AnotherID INT
,AnotherName VARCHAR(50)
)
AS
BEGIN
DECLARE @List TABLE (
ID INT
,Name VARCHAR(40)
,ParentID INT
)
DECLARE List CURSOR
FOR
SELECT ID
FROM YourTable WITH (NOLOCK)
WHERE CompanyID IN (
SELECT companyid
FROM UserToCompany
WHERE UserName = @UserName
)
DECLARE @ID AS INT
OPEN List
FETCH List
INTO @id
WHILE (@@FETCH_STATUS = 0)
BEGIN
INSERT INTO @List
SELECT *
FROM ufnIncludeWithChildren(@ID)
FETCH List
INTO @ID
END
CLOSE List
DEALLOCATE List
INSERT INTO @User
SELECT DISTINCT a.ID
,a.Name
,a.ParentID
,c.CompanyID
,c.CompanyName
,c.CompanyDisplayName
,d.AnotherID
,d.AnotherName
FROM @List a
JOIN YourTable b ON a.ID = b.ID
JOIN CompanyInfo c ON b.CompanyID = c.CompanyID
JOIN Anothers d ON b.AnotherID = d.AnotherID
RETURN
END
Комментарии:
1. О боже… Первое правило — никогда не использовать курсоры. Тот, что в вашей процедуре, можно легко заменить циклом WHILE. Я посмотрю, смогу ли я это оптимизировать.
2. @Ruslan и второе правило — используйте FAST_FORWARD, если все, что вы делаете, это зацикливание
3. @Conraddfrix Это круто, приятно знать! Однако я стараюсь избегать их вообще, когда это возможно. До этого дня я никогда не видел случая использования, когда их нельзя было бы заменить другой конструкцией (даже в действительно сложных процедурах с 10k LOC).
Ответ №1:
Вы должны иметь возможность использовать один select.
USE [DB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[ufnGetForUser] (@UserName VARCHAR(100))
RETURNS @User TABLE (
ID INT
,Name VARCHAR(40)
,ParentID INT
,CompanyID INT
,CompanyName VARCHAR(100)
,CompanyDisplayName VARCHAR(100)
,AnotherID INT
,AnotherName VARCHAR(50)
)
AS
BEGIN
INSERT INTO @User
SELECT DISTINCT a.ID
,a.Name
,a.ParentID
,c.CompanyID
,c.CompanyName
,c.CompanyDisplayName
,d.AnotherID
,d.AnotherName
FROM UserToCompany u
JOIN CompanyInfo c ON u.CompanyID = c.CompanyID
CROSS APPLY ufnIncludeWithChildren(yt.ID) a
JOIN YourTable b ON a.ID = b.ID
JOIN Anothers d ON b.AnotherID = d.AnotherID
WHERE u.UserName = @UserName
RETURN
END
Комментарии:
1. Спасибо! Пробуем это сейчас. Одна из проблем заключается в том, что нет c. ID итак, я пытаюсь понять, как вы это сделали.
2. @JoJo Хорошо, я понял. У этой таблицы изначально отсутствовало название. Я думаю, что обновление к моему ответу решает эту проблему.
3. Отлично! Я думаю, что соединение <TABLE> b На yt. ID = b.ID не представляет таблицу?
4. Привет! Выполнение запроса Select DISTINCT с объединением таблиц заняло 4 минуты.
5. @JoJo Именно с этого вам нужно начать просмотр планов выполнения. Я не вижу ничего неэффективного в моем SQL, поэтому без SQL-скрипта с образцами данных я не уверен, насколько еще я мог бы помочь.
Ответ №2:
На первый взгляд, мы определенно можем исключить CURSOR
. Хотя замена его WHILE
циклом, как в моем комментарии, является одним из решений, мы можем пойти еще дальше и полностью исключить цикл, используя APPLY
предложение для вызова функции непосредственно для каждого значения в строке.
ALTER FUNCTION [dbo].[ufnGetForUser] (@UserName VARCHAR(100))
RETURNS @User TABLE (
ID INT
,Name VARCHAR(40)
,ParentID INT
,CompanyID INT
,CompanyName VARCHAR(100)
,CompanyDisplayName VARCHAR(100)
,AnotherID INT
,AnotherName VARCHAR(50)
)
AS
BEGIN
DECLARE @List TABLE (
ID INT
,Name VARCHAR(40)
,ParentID INT
)
INSERT INTO @List
SELECT sfn.*
FROM YourTable yt WITH (NOLOCK)
CROSS APPLY ufnIncludeWithChildren(yt.ID) sfn
WHERE yt.CompanyID IN (
SELECT companyid
FROM UserToCompany
WHERE UserName = @UserName
)
INSERT INTO @User
SELECT DISTINCT a.ID
,a.Name
,a.ParentID
,c.CompanyID
,c.CompanyName
,c.CompanyDisplayName
,d.AnotherID
,d.AnotherName
FROM @List a
JOIN b ON a.ID = b.ID
JOIN CompanyInfo c ON b.CompanyID = c.CompanyID
JOIN Anothers d ON b.AnotherID = d.AnotherID
RETURN
END
Этот код не тестировался, но должен работать. Пожалуйста, дайте мне знать, если у вас возникнут какие-либо проблемы.
Кроме того, в вашем вопросе вы опустили имя таблицы из первого запроса (где у вас был курсор), поэтому я вставил YourTable
туда в качестве заполнителя.
Комментарии:
1. Отлично! Пожалуйста, сделайте. APPLY — это способ выполнения функции для каждой строки результата; это похоже на использование подзапроса, но во многих случаях в несколько раз быстрее (я видел увеличение производительности в 1000 раз), потому что это позволяет SQL Server лучше оптимизировать его. Существует ПЕРЕКРЕСТНОЕ ПРИМЕНЕНИЕ (исключение строк, которые не возвращают результат) и ВНЕШНЕЕ ПРИМЕНЕНИЕ (как ЛЕВОЕ СОЕДИНЕНИЕ, но против функции).
2. Ответ @SpectralGhost еще лучше, потому что он делает еще один шаг вперед и полностью устраняет временную таблицу ‘List’. В остальном это та же оптимизация, но сделан еще один шаг вперед. Таким образом, его решение должно быть еще быстрее
3. О боже!!.. почти мгновенно теперь в веб-приложении!! Мне есть ЧЕМУ поучиться только на одном вашем примере. Я сам выполняю базовые операции CRUD, поэтому я все еще учусь, и это не моя оригинальная функция UFN.
4. @JoJo Да, оптимизация SQL на самом деле очень увлекательна, как только вы в нее вникнете. На одной из моих старых (с большим объемом SQL) заданий у нас на экране была панель мониторинга со средним временем выполнения для всех хранимых процедур в системе (десятки тысяч), и разработчики получали вознаграждение за оптимизацию самых длительных 🙂 Это становится второй натурой, как только вы оптимизируете несколько!
5. @JoJo Сколько данных возвращается? Конечно, все зависит от вашего конкретного варианта использования и т.д., Но ничто не должно занимать 14 секунд, если это не огромный объем данных… Вы просмотрели планы выполнения, чтобы убедиться, что все индексы на месте?