Мультизвернення TVF проти Inline TVF Performance


18

Порівнюючи деякі відповіді на питання 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;

Коментарі не для розширеного обговорення; ця розмова переміщена до чату .
Пола Вайт Відновити Моніку

Відповіді:


12

Таблиця ваших чисел є групою і потенційно може бути повністю сканується кожен раз.

Додайте кластеризований первинний ключ Numberі спробуйте наступне з forceseekпідказкою, щоб отримати потрібну пошукову запит .

Наскільки я можу сказати, ця підказка потрібна, оскільки SQL Server просто оцінює, що 27% таблиці відповідатиме предикату (30% для <=та зменшено до 27% <>). А отже, йому доведеться прочитати лише 3-4 рядки, перш ніж знайти той, який відповідає, і він може вийти з напівз'єднання. Тож варіант сканування коштує дуже дешево. Але насправді, якщо є якісь паліндроми, тоді доведеться прочитати всю таблицю, тому це не гарний план.

CREATE FUNCTION dbo.InlineIsPalindrome
(
    @Word NVARCHAR(500)
)
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN (
    WITH Nums AS
    (
      SELECT
        N = number
      FROM
        dbo.Numbers WITH(FORCESEEK)
    )
    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

З цими змінами на місці він летить для мене (займає 228 мс)

введіть тут опис зображення

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