다중 문장 TVF 및 인라인 TVF 성능


18

Palindrome 질문 에 대한 답변 중 일부를 비교하면 (답을 삭제 한 이후 10k + 사용자 만 해당) 혼란스러운 결과가 나타납니다.

나는 표준 함수를 실행하는 것보다 더 빠를 것으로 생각 되는 다중 문, 스키마 바운드 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, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0.
테이블 '작업 테이블'. 스캔 횟수 0, 논리적 읽기 0, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0.
테이블 '#Words'. 스캔 카운트 1, 논리적 읽기 88, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0.

SQL Server 실행 시간 :
CPU 시간 = 1700ms, 경과 시간 = 2022ms
SQL Server 구문 분석 및 컴파일 시간 :
CPU 시간 = 0ms, 경과 시간 = 0ms

인라인 TVF :

테이블 '숫자'. 스캔 카운트 1, 논리적 읽기 1272030, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0.
테이블 '작업 테이블'. 스캔 횟수 0, 논리적 읽기 0, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0.
테이블 '#Words'. 스캔 카운트 1, 논리적 읽기 88, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0.

SQL Server 실행 시간 :
CPU 시간 = 137874ms, 경과 시간 = 139415ms
SQL Server 구문 분석 및 컴파일 시간 :
CPU 시간 = 0ms, 경과 시간 = 0ms

실행 계획은 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

여기에 이미지 설명을 입력하십시오

이 경우 인라인 변형이 다중 문 변형보다 훨씬 느린 이유는 무엇입니까?

@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, lob 논리적 읽기 0, lob 물리적 읽기 0, lob read-ahead 읽기 0.
테이블 '작업 테이블'. 스캔 횟수 0, 논리적 읽기 0, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0.
테이블 '#Words'. 스캔 횟수 1, 논리적 읽기 90, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0

SQL Server 실행 시간 :
CPU 시간 = 1763ms, 경과 시간 = 2192ms

dbo.FunctionIsPalindrome(w.Word):

(17424 행에 영향을 미침)
테이블 '작업 테이블'. 스캔 횟수 0, 논리적 읽기 0, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0.
테이블 '#Words'. 스캔 횟수 1, 논리적 읽기 90, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0

SQL Server 실행 시간 :
CPU 시간 = 328ms, 경과 시간 = 424ms

CROSS APPLY dbo.InlineIsPalindrome(w.Word) p:

(17424 행에 영향을 미침)
테이블 '숫자'. 스캔 카운트 1, 논리적 읽기 237100, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob read-ahead 읽기 0.
테이블 '작업 테이블'. 스캔 횟수 0, 논리적 읽기 0, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0.
테이블 '#Words'. 스캔 횟수 1, 논리적 읽기 90, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0

SQL Server 실행 시간 :
CPU 시간 = 17737ms, 경과 시간 = 17946ms

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;

의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
Paul White는 GoFundMonica가

답변:


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

그 변화로 제게 날아갑니다 (228ms 걸립니다)

여기에 이미지 설명을 입력하십시오

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.