Сравнивая некоторые ответы на вопрос о Палиндроме (только 10 000 пользователей, поскольку я удалил ответ), я получаю запутанные результаты.
Я предложил TVF с привязкой к нескольким операторам, который, по моему мнению, будет быстрее, чем запуск стандартной функции. У меня также сложилось впечатление, что TVF с несколькими утверждениями будет «встроенным», хотя я ошибаюсь в этом отношении, как вы увидите ниже. Этот вопрос о разнице в производительности этих двух стилей TVF. Сначала вам нужно увидеть код.
Вот многократное заявление TVF:
IF OBJECT_ID('dbo.IsPalindrome') IS NOT NULL
DROP FUNCTION dbo.IsPalindrome;
GO
CREATE FUNCTION dbo.IsPalindrome
(
@Word NVARCHAR(500)
)
RETURNS @t TABLE
(
IsPalindrome BIT NOT NULL
)
WITH SCHEMABINDING
AS
BEGIN
DECLARE @IsPalindrome BIT;
DECLARE @LeftChunk NVARCHAR(250);
DECLARE @RightChunk NVARCHAR(250);
DECLARE @StrLen INT;
DECLARE @Pos INT;
SET @RightChunk = '';
SET @IsPalindrome = 0;
SET @StrLen = LEN(@Word) / 2;
IF @StrLen % 2 = 1 SET @StrLen = @StrLen - 1;
SET @Pos = LEN(@Word);
SET @LeftChunk = LEFT(@Word, @StrLen);
WHILE @Pos > (LEN(@Word) - @StrLen)
BEGIN
SET @RightChunk = @RightChunk + SUBSTRING(@Word, @Pos, 1)
SET @Pos = @Pos - 1;
END
IF @LeftChunk = @RightChunk SET @IsPalindrome = 1;
INSERT INTO @t VALUES (@IsPalindrome);
RETURN
END
GO
Встроенный TVF:
IF OBJECT_ID('dbo.InlineIsPalindrome') IS NOT NULL
DROP FUNCTION dbo.InlineIsPalindrome;
GO
CREATE FUNCTION dbo.InlineIsPalindrome
(
@Word NVARCHAR(500)
)
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN (
WITH Nums AS
(
SELECT
N = number
FROM
dbo.Numbers
)
SELECT
IsPalindrome =
CASE
WHEN EXISTS
(
SELECT N
FROM Nums
WHERE N <= L / 2
AND SUBSTRING(S, N, 1) <> SUBSTRING(S, 1 + L - N, 1)
)
THEN 0
ELSE 1
END
FROM
(SELECT LTRIM(RTRIM(@Word)), LEN(@Word)) AS v (S, L)
);
GO
Numbers
Таблица в приведенной выше функции определяются следующим образом:
CREATE TABLE dbo.Numbers
(
Number INT NOT NULL
);
Примечание. Таблица чисел не имеет индексов и первичного ключа и содержит 1 000 000 строк.
Временный стол испытательного стенда:
IF OBJECT_ID('tempdb.dbo.#Words') IS NOT NULL
DROP TABLE #Words;
GO
CREATE TABLE #Words
(
Word VARCHAR(500) NOT NULL
);
INSERT INTO #Words(Word)
SELECT o.name + REVERSE(w.name)
FROM sys.objects o
CROSS APPLY (
SELECT o.name
FROM sys.objects o
) w;
В моей тестовой системе вышеприведенные INSERT
результаты приводят к добавлению 16 900 строк в #Words
таблицу.
Чтобы проверить два варианта, я SET STATISTICS IO, TIME ON;
и использую следующее:
SELECT w.Word
, p.IsPalindrome
FROM #Words w
CROSS APPLY dbo.IsPalindrome(w.Word) p
ORDER BY w.Word;
SELECT w.Word
, p.IsPalindrome
FROM #Words w
CROSS APPLY dbo.InlineIsPalindrome(w.Word) p
ORDER BY w.Word;
Я ожидал, что InlineIsPalindrome
версия будет значительно быстрее, однако следующие результаты не поддерживают это предположение.
Мульти-оператор TVF:
Таблица «# A1CE04C3». Число сканирований 16896, логическое чтение 16900, физическое чтение 0, чтение с опережением 0, логическое чтение с
бита 0, физическое чтение с бита 0, чтение с опережением чтения 0. Таблица «Рабочий стол». Сканирование счетчик 0, логическое чтение 0, физическое чтение 0, чтение с опережением 0, логическое чтение с
бита 0, физическое чтение с бита 0, чтение с опережением чтения 0. Таблица «#Words». Сканирование 1, логическое чтение 88, физическое чтение 0, чтение с опережением 0, логическое чтение с 0, физическое чтение с 0, чтение с опережением 0.Время выполнения SQL Server:
время ЦП = 1700 мс, прошедшее время = 2022 мс.
Время анализа и компиляции SQL Server: время
ЦП = 0 мс, прошедшее время = 0 мс.
Встроенный TVF:
Таблица «Числа». Сканирование 1, логическое чтение 1272030, физическое чтение 0, чтение с опережением 0, логическое чтение с 0, физическое чтение с 0, чтение с опережением 0.
Таблица «Рабочий стол». Сканирование счетчик 0, логическое чтение 0, физическое чтение 0, чтение с опережением 0, логическое чтение с
бита 0, физическое чтение с бита 0, чтение с опережением чтения 0. Таблица «#Words». Сканирование 1, логическое чтение 88, физическое чтение 0, чтение с опережением 0, логическое чтение с 0, физическое чтение с 0, чтение с опережением 0.Время выполнения SQL Server:
время ЦП = 137874 мс, прошедшее время = 139415 мс.
Время анализа и компиляции SQL Server: время
ЦП = 0 мс, прошедшее время = 0 мс.
Планы выполнения выглядят так:
Почему встроенный вариант в этом случае намного медленнее, чем вариант с несколькими утверждениями?
В ответ на комментарий @AaronBertrand я изменил dbo.InlineIsPalindrome
функцию, чтобы ограничить строки, возвращаемые CTE, для соответствия длине входного слова:
CREATE FUNCTION dbo.InlineIsPalindrome
(
@Word NVARCHAR(500)
)
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN (
WITH Nums AS
(
SELECT
N = number
FROM
dbo.Numbers
WHERE
number <= LEN(@Word)
)
SELECT
IsPalindrome =
CASE
WHEN EXISTS
(
SELECT N
FROM Nums
WHERE N <= L / 2
AND SUBSTRING(S, N, 1) <> SUBSTRING(S, 1 + L - N, 1)
)
THEN 0
ELSE 1
END
FROM
(SELECT LTRIM(RTRIM(@Word)), LEN(@Word)) AS v (S, L)
);
Как предложил @MartinSmith, я добавил в dbo.Numbers
таблицу первичный ключ и кластеризованный индекс , что, безусловно, помогает и будет ближе к тому, что можно ожидать в производственной среде.
Повторное выполнение тестов, приведенных выше, теперь приводит к следующей статистике:
CROSS APPLY dbo.IsPalindrome(w.Word) p
:
(Затронуто 17424 строк)
Таблица '# B1104853'. Число сканирований 17420, логическое чтение 17424, физическое чтение 0, чтение с опережением 0, логическое чтение с бита 0, физическое чтение с бита 0, чтение с опережением чтения 0.
Таблица «Рабочий стол». Сканирование счетчик 0, логическое чтение 0, физическое чтение 0, чтение с опережением 0, логическое чтение с
бита 0, физическое чтение с бита 0, чтение с опережением чтения 0. Таблица «#Words». Сканирование 1, логическое чтение 90, физическое чтение 0, чтение с опережением 0, логическое чтение с 0, физическое чтение с 0, чтение с опережением 0.Время выполнения SQL Server:
время ЦП = 1763 мс, прошедшее время = 2192 мс.
dbo.FunctionIsPalindrome(w.Word)
:
(Затронуто 17424 строк)
Таблица 'Рабочий стол'. Сканирование счетчик 0, логическое чтение 0, физическое чтение 0, чтение с опережением 0, логическое чтение с
бита 0, физическое чтение с бита 0, чтение с опережением чтения 0. Таблица «#Words». Сканирование 1, логическое чтение 90, физическое чтение 0, чтение с опережением 0, логическое чтение с 0, физическое чтение с 0, чтение с опережением 0.Время выполнения SQL Server:
время ЦП = 328 мс, прошедшее время = 424 мс.
CROSS APPLY dbo.InlineIsPalindrome(w.Word) p
:
(Затронуты 17424 строки)
Таблица «Числа». Сканирование 1, логическое чтение 237100, физическое чтение 0, чтение с опережением 0, чтение логического объекта 0, физическое чтение 1, чтение с опережением 0.
Таблица «Рабочий стол». Сканирование счетчик 0, логическое чтение 0, физическое чтение 0, чтение с опережением 0, логическое чтение с
бита 0, физическое чтение с бита 0, чтение с опережением чтения 0. Таблица «#Words». Сканирование 1, логическое чтение 90, физическое чтение 0, чтение с опережением 0, логическое чтение с 0, физическое чтение с 0, чтение с опережением 0.Время выполнения SQL Server:
время ЦП = 17737 мс, прошедшее время = 17946 мс.
Я тестирую это на SQL Server 2012 SP3, v11.0.6020, Developer Edition.
Вот определение моей таблицы чисел с первичным ключом и кластерным индексом:
CREATE TABLE dbo.Numbers
(
Number INT NOT NULL
CONSTRAINT PK_Numbers
PRIMARY KEY CLUSTERED
);
;WITH n AS
(
SELECT v.n
FROM (
VALUES (1)
,(2)
,(3)
,(4)
,(5)
,(6)
,(7)
,(8)
,(9)
,(10)
) v(n)
)
INSERT INTO dbo.Numbers(Number)
SELECT ROW_NUMBER() OVER (ORDER BY n1.n)
FROM n n1
, n n2
, n n3
, n n4
, n n5
, n n6;