Порівнюючи деякі відповіді на питання Palindrome (лише 10 к + користувачів, оскільки я видалив відповідь), я отримую заплутані результати.
Я запропонував мульти-оператор, пов'язаний із схемою 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
Inline-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, лобічне фізичне зчитування 0, лобічне зчитування лобове читання 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, версії для розробників.
Ось визначення таблиці моїх чисел з первинним ключем та кластерним індексом:
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;