선택을 추가 할 때 자체 참조 스칼라 함수 중첩 레벨이 초과되었습니다.


24

목적

자체 참조 기능의 테스트 예제를 작성하려고 할 때 한 버전은 실패하고 다른 버전은 성공합니다.

유일한 차이점 SELECT은 함수 본문에 추가 되어 두 실행 계획이 다릅니다.


작동하는 기능

CREATE FUNCTION dbo.test5(@i int)
RETURNS INT
AS 
BEGIN
RETURN(
SELECT TOP 1
CASE 
WHEN @i = 1 THEN 1
WHEN @i = 2 THEN 2
WHEN @i = 3 THEN  dbo.test5(1) + dbo.test5(2)
END
)
END;

함수 호출

SELECT dbo.test5(3);

보고

(No column name)
3

작동하지 않는 기능

CREATE FUNCTION dbo.test6(@i int)
RETURNS INT
AS 
BEGIN
RETURN(
SELECT TOP 1
CASE 
WHEN @i = 1 THEN 1
WHEN @i = 2 THEN 2
WHEN @i = 3 THEN (SELECT dbo.test6(1) + dbo.test6(2))
END
)END;

함수 호출

SELECT dbo.test6(3);

또는

SELECT dbo.test6(2);

오류 발생

최대 저장 프로 시저, 함수, 트리거 또는 뷰 중첩 수준을 초과했습니다 (제한 32).

원인을 추측

실패한 함수의 예상 계획에 대한 추가 계산 스칼라가 있습니다.

<ColumnReference Column="Expr1002" />
<ScalarOperator ScalarString="CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END">

그리고 expr1000은

<ColumnReference Column="Expr1000" />
<ScalarOperator ScalarString="[dbo].[test6]((1))+[dbo].[test6]((2))">

32를 초과하는 재귀 참조를 설명 할 수 있습니다.

실제 질문

추가 SELECT하면 함수 자체가 계속 반복되어 무한 루프가 발생하지만 왜이 SELECT결과를 제공합니까?


추가 정보

예상 실행 계획

DB <> 피들

Build version:
14.0.3045.24

compatibility_levels 100 및 140에서 테스트되었습니다.

답변:


26

이것은 프로젝트 정규화 의 버그이며 , 결정적이지 않은 함수가있는 case 표현식 내에서 하위 쿼리를 사용하여 노출됩니다.

설명하기 위해, 우리는 두 가지를 미리주의해야합니다.

  1. SQL Server는 하위 쿼리를 직접 실행할 수 없으므로 항상 롤을 풀거나 apply 로 변환합니다 .
  2. 의 의미는 CASE하도록되어 THEN경우 표현은 평가되어야한다 WHEN절은 true를 반환합니다.

문제가있는 경우에 도입 된 (사소한) 하위 쿼리는 적용 연산자 (중복 루프 조인)를 발생시킵니다. 두 번째 요구 사항을 충족하기 위해 SQL Server는 처음에 표현식 dbo.test6(1) + dbo.test6(2)을 적용의 내부에 배치합니다.

강조 표시된 계산 스칼라

[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))

... 결합에 대한 통과 술어 가 CASE의미 를 갖는 의미 :

[@i]=(1) OR [@i]=(2) OR IsFalseOrNull [@i]=(3)

루프의 내부는 통과 조건이 false (의미 @i = 3)로 평가되는 경우에만 평가됩니다 . 이것은 지금까지 모두 정확합니다. 중첩 루프 조인 뒤 의 계산 스칼라CASE의미를 올바르게 존중 합니다.

[Expr1001] = Scalar Operator(CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END)

문제는 쿼리 컴파일 의 프로젝트 정규화 단계에서 Expr1000상관 관계가없는 것을보고 루프 외부로 이동하는 것이 안전하다는 것을 결정한다는 것입니다 ( 내레이터 : 그렇지 않습니다 ).

프로젝트 이동

[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))

이것은 pass-through 술어에 의해 구현 된 의미론을 깨뜨 리므로 * 함수는 없어야 할 때 평가되며 무한 루프 결과입니다.

이 버그를보고해야합니다. 해결 방법은 표현식이 상관 관계에 따라 (즉 @i, 표현식에 포함) 적용 외부로 이동되지 않도록하는 것 입니다. 물론 이것은 해킹입니다. 프로젝트 정규화를 비활성화하는 방법이 있지만 공개적으로 공유하지 않도록 요청 받았으므로 그렇게하지 않습니다.

인라인 논리가 구문 분석 된 트리에서 직접 작동하기 때문에 (프로젝트 정규화 이전에) 스칼라 함수가 인라인 될 때 SQL Server 2019에서이 문제가 발생하지 않습니다 . 문제의 간단한 논리는 비 재귀에 대한 인라인 논리로 단순화 할 수 있습니다.

[Expr1019] = (Scalar Operator((1)))
[Expr1045] = Scalar Operator(CONVERT_IMPLICIT(int,CONVERT_IMPLICIT(int,[Expr1019],0)+(2),0))

... 3을 반환합니다.

핵심 문제를 설명하는 또 다른 방법은 다음과 같습니다.

-- Not schema bound to make it non-det
CREATE OR ALTER FUNCTION dbo.Error() 
RETURNS integer 
-- WITH INLINE = OFF -- SQL Server 2019 only
AS
BEGIN
    RETURN 1/0;
END;
GO
DECLARE @i integer = 1;

SELECT
    CASE 
        WHEN @i = 1 THEN 1
        WHEN @i = 2 THEN 2
        WHEN @i = 3 THEN (SELECT dbo.Error()) -- 'subquery'
        ELSE NULL
    END;

2008 R2에서 2019 CTP 3.0까지 모든 버전의 최신 빌드를 재현합니다.

Martin Smith가 제공하는 추가 예 (스칼라 함수 없음) :

SELECT IIF(@@TRANCOUNT >= 0, 1, (SELECT CRYPT_GEN_RANDOM(4)/ 0))

여기에는 필요한 모든 핵심 요소가 있습니다.

  • CASE(로 내부적으로 구현 됨 ScaOp_IIF)
  • 비 결정적 함수 ( CRYPT_GEN_RANDOM)
  • 실행해서는 안되는 분기의 하위 쿼리 ( (SELECT ...))

* 엄격히, 위의 변환은 Expr1000안전한 구조에서만 참조되기 때문에 평가가 올바르게 연기 된 경우에도 여전히 정확할 수 있습니다 .

[Expr1002] = Scalar Operator(CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END)

...하지만 내부 ForceOrder 플래그 (쿼리 힌트 아님) 가 필요하지만 설정되지 않았습니다. 어쨌든, 프로젝트 정규화 에 의해 적용된 로직의 구현 은 부정확하거나 불완전합니다.

SQL Server에 대한 Azure 피드백 사이트의 버그 보고서

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