SQL Server는 A <> B를 A <B OR A> B로 분할하여 B가 결정적이지 않은 경우 이상한 결과를 생성합니다.


26

SQL Server에 흥미로운 문제가 발생했습니다. 다음 재현 예제를 고려하십시오.

CREATE TABLE #test (s_guid uniqueidentifier PRIMARY KEY);
INSERT INTO #test (s_guid) VALUES ('7E28EFF8-A80A-45E4-BFE0-C13989D69618');

SELECT s_guid FROM #test
WHERE s_guid = '7E28EFF8-A80A-45E4-BFE0-C13989D69618'
  AND s_guid <> NEWID();

DROP TABLE #test;

깡깡이

s_guid <> NEWID()조건이 완전히 쓸모없는 것처럼 보이는 것을 잊지 마십시오 . 이것은 최소한의 재현 사례입니다. NEWID()주어진 상수 값 을 일치 시킬 확률 은 매우 작으므로 매번 TRUE로 평가해야합니다.

그러나 그렇지 않습니다. 이 쿼리를 실행하면 보통 1 개 행을 돌려줍니다 만, 때때로 (매우 자주, 10 명 중 1 개 이상 시간) 0 행을 반환합니다. 시스템에서 SQL Server 2008을 사용하여이를 재현했으며 위에 링크 된 바이올린 (SQL Server 2014)을 사용하여 온라인으로이를 재현 할 수 있습니다.

실행 계획을 보면 쿼리 분석기가 분명히 조건을 s_guid < NEWID() OR s_guid > NEWID()다음과 같이 나눕니다 .

쿼리 계획 스크린 샷

... 때때로 실패하는 이유를 완전히 설명합니다 (첫 번째로 생성 된 ID가 작고 두 번째 ID가 주어진 ID보다 큰 경우).

SQL Server를 평가하기 위해 허용 A <> B과 같은 A < B OR A > B표현 중 하나가 비 결정적 경우에도? 그렇다면 어디에 기록되어 있습니까? 아니면 버그를 찾았습니까?

흥미롭게도 AND NOT (s_guid = NEWID())동일한 실행 계획 (및 동일한 임의의 결과)을 산출합니다.

개발자가 선택적으로 특정 행을 제외하고 싶을 때이 문제를 발견했습니다.

s_guid <> ISNULL(@someParameter, NEWID())

"바로 가기"로 :

(@someParameter IS NULL OR s_guid <> @someParameter)

버그에 대한 설명서 및 / 또는 확인을 찾고 있습니다. 코드가 모두 관련이있는 것은 아니므로 해결 방법이 필요하지 않습니다.


답변:


22

SQL Server를 평가하기 위해 허용 A <> B과 같은 A < B OR A > B표현 중 하나가 비 결정적 경우에도?

이것은 다소 논란의 여지가있는 점이며, 그 대답은 "예"입니다.

내가 아는 가장 좋은 토론은 Itzik Ben-Gan의 Connect 버그 보고서 Bug with NEWID 및 Table Expressions 에 대한 답변으로 주어졌으며 수정되지 않았으므로 닫혔습니다. 연결이 종료 된 이후 웹 아카이브에 대한 링크가 있습니다. 안타깝게도 Connect의 소멸로 인해 유용한 자료가 많이 손실되었습니다 (또는 찾기가 어려워졌습니다). 어쨌든 Microsoft의 Jim Hogg에서 가장 유용한 인용문은 다음과 같습니다.

이것은 문제의 핵심에 맞습니다. 최적화가 프로그램의 의미를 바꿀 수 있습니까? 즉, 프로그램이 특정 답변을 제공하지만 느리게 실행되는 경우 Query Optimizer가 해당 프로그램을 더 빨리 실행하면서 주어진 결과를 변경하는 것이 합법적인가요?

"아니오!" (나 자신의 개인적인 성향도 :-), 좋은 소식은 99 %의 경우 대답이 동일하다는 것입니다. 따라서 쿼리 최적화는 확실한 승리입니다. 나쁜 소식은 쿼리에 부작용 코드가 포함되어 있으면 다른 계획으로 인해 실제로 다른 결과가 나올 수 있다는 것입니다. 그리고 NEWID ()는 그 차이를 드러내는 부작용 (비 결정적) '함수'중 하나입니다. [실제로 실험을하는 경우 AND 절의 단락 평가와 같은 다른 항목을 고안 할 수 있습니다. 두 번째 절이 0으로 산술 나누기-첫 번째 절 전에 다른 최적화에서 두 번째 절을 실행할 수 있음] 이 스레드의 다른 곳에서 Craig의 설명은 SqlServer가 스칼라 연산자가 실행될 때를 보장하지 않는다고 설명합니다.

따라서 결정적이지 않은 (부수적 인) 코드가있는 상태에서 특정 동작을 보장하려면 JOIN의 결과 (예 : 중첩 루프 실행의 의미를 따르도록)를 선택해야합니다. UC가 지적한 것처럼 적절한 옵션을 사용하여 해당 동작을 강제 실행할 수 있습니다. 그러나 결과 코드는 느리게 실행됩니다. 이는 실제로 쿼리 최적화 프로그램을 방해하는 비용입니다.

우리는 NEWID ()에 대해 "예상대로"동작 방향으로 쿼리 최적화 프로그램을 이동하고 있습니다.

이와 관련하여 시간이 지남에 따라 동작이 변경되는 한 가지 예는 NULLIF가 RAND ()와 같은 비 결정적 함수에서 잘못 작동 한다는 것 입니다. 예를 들어 COALESCE예상치 못한 결과를 생성 할 수 있고 점진적으로 해결되는 하위 쿼리와 함께 다른 유사한 경우 도 있습니다.

Jim은 계속합니다.

루프 닫기. . . 이 질문을 Dev 팀과 논의했습니다. 결국 우리는 다음과 같은 이유로 현재의 행동을 바꾸지 않기로 결정했습니다.

1) 옵티마이 저는 스칼라 함수의 타이밍 또는 실행 횟수를 보장하지 않습니다. 이것은 오랫동안 확립 된 신조입니다. 최적화 프로그램이 쿼리 계획 실행에서 상당한 개선을 얻을 수있는 충분한 자유를 허용하는 기본 '여정'입니다.

2)이 "행당 한 번의 행동"은 널리 논의되지는 않지만 새로운 문제는 아닙니다. 우리는 유콘 릴리스에서 그 동작을 다시 조정하기 시작했습니다. 그러나 모든 경우에 정확히 그것이 의미하는 바를 정확히 찾아내는 것은 매우 어렵습니다! 예를 들어, 최종 결과에 대해 '길에서'계산 된 중간 행에 적용됩니까? -이 경우 선택한 계획에 따라 달라집니다. 아니면 결국 완성 된 결과에 나타날 행에만 적용됩니까? -당신이 동의 할 것이라고 확신하기 때문에 불쾌한 재귀가 여기에 있습니다!

3) 앞에서 언급했듯이 기본적으로 '성능 최적화'를 사용하며 99 %의 사례에 적합합니다. 결과가 변경 될 수있는 경우의 1 %는 NEWID와 같은 부작용을 발견하고 수정하기가 쉽습니다 (결과적으로 거래 성능). 이 기본값은 다시 "성능 최적화"로 설정되어 오랫동안 설정되어 승인됩니다. (예, 전통적인 프로그래밍 언어를 위해 컴파일러가 선택한 자세는 아니지만 그렇게해야합니다).

따라서 권장 사항은 다음과 같습니다.

a) 보장되지 않은 타이밍과 실행 횟수 의미에 의존하지 마십시오. b) 테이블 표현식에 NEWID ()를 사용하지 마십시오. c) OPTION을 사용하여 특정 행동을 강요하십시오 (거래 실적)

이 설명이이 버그를 종료 한 이유를 "수정하지 않음"으로 명확히하는 데 도움이되기를 바랍니다.


흥미롭게도 AND NOT (s_guid = NEWID())동일한 실행 계획을 산출합니다

이는 정규화의 결과이며 쿼리 컴파일 중에 매우 일찍 발생합니다. 두 표현식 모두 정확히 동일한 정규화 된 형식으로 컴파일되므로 동일한 실행 계획이 생성됩니다.


이 경우 문제를 피하기 위해 특정 계획을 강요하려는 경우 WITH (FORCESCAN)을 사용할 수 있습니다. 확실히하기 위해 쿼리를 실행하기 전에 변수를 사용하여 NEWID () 결과를 저장해야합니다.
Razvan Socol

11

이것은 여기에 (다양한) 문서화되어 있습니다 :

쿼리에 지정된 함수가 실제로 실행되는 횟수는 옵티마이 저가 작성한 실행 계획에 따라 달라질 수 있습니다. 예를 들면 WHERE 절에서 부속 조회에 의해 호출 된 함수가 있습니다. 서브 쿼리 및 해당 기능이 실행되는 횟수는 옵티마이 저가 선택한 다른 액세스 경로에 따라 달라질 수 있습니다.

사용자 정의 함수

조회 계획이 NEWID ()를 여러 번 실행하고 결과를 변경하는 유일한 조회 양식은 아닙니다. 이것은 혼란 스럽지만 실제로 NEWID ()가 키 생성 및 임의 정렬에 유용하기 위해서는 매우 중요합니다.

가장 혼란스러운 것은 결정적이지 않은 모든 함수가 실제로 이와 같이 동작 하지는 않는다는 것 입니다. 예를 들어 RAND () 및 GETDATE ()는 쿼리 당 한 번만 실행됩니다.


엔진이 "같지 않음"을 범위로 변환하는 이유 / 언제를 설명하는 블로그 게시물 또는 이와 유사한 것이 있습니까?
Mister Magoo

3
내가 아는 한에서는 아니다. 때문에 일상이 될 수 있습니다 =, <그리고 >효율적으로 BTREE에 대해 평가 될 수있다.
David Browne-Microsoft

5

가치있는 것에 대해,이 오래된 SQL 92 표준 문서 를 보면 불평등에 대한 요구 사항 8.2 <comparison predicate>은 다음과 같이 " " 섹션에 설명되어 있습니다.

1) X와 Y는 두 개의 해당 <행 값 생성자 요소>입니다. XV와 YV를 각각 X와 Y로 표시되는 값으로 설정하십시오.

[...]

ii) "X <> Y"는 XV와 YV가 같지 않은 경우에만 참입니다.

[...]

7) Rx와 Ry를 <comparison predicate>의 두 개의 <행 값 생성자>로하고 RXi와 RYi를 각각 Rx와 Ry의 i 번째 <행 값 생성자 요소>로 둡니다. "Rx <comp op> Ry"는 다음과 같이 true, false 또는 unknown입니다.

[...]

b) "x <> Ry"는 일부 i에 대해 RXi <> RYi 인 경우에만 해당됩니다.

[...]

h) "Rx = Ry"가 참인 경우에만 "x <> Ry"는 거짓입니다.

참고 : <>비교 에 대해 이야기했기 때문에 완전성을 위해 7b와 7h를 포함 했습니다. 여러 값으로 행 값 생성자를 비교하는 것이 T-SQL에서 구현되지 않는다고 생각하지 않습니다.

이것은 혼란스러운 쓰레기입니다. 하지만 쓰레기 수거통 다이빙을 계속하고 싶다면 ...

내가 생각 하는 1.ii 우리의 값을 비교하고 있기 때문에,이 시나리오에 적용되는 항목이다 "행 값 생성자 요소를."

ii) "X <> Y"는 XV와 YV가 같지 않은 경우에만 참입니다.

기본적으로 X와 Y가 나타내는 이 같지 않다는 X <> Y것이 사실 입니다. 이후 그 술어의 논리적 상당 재 작성 최적화가를 사용하는 것은 완전히 멋지다.X < Y OR X > Y

표준은 <>비교 연산자 의 양쪽에있는 행 값 생성자 요소의 결정 성 (또는 얻는 것이 무엇이든)과 관련된이 정의에 제약을 두지 않습니다 . 한쪽의 값 표현이 비 결정적 일 수 있다는 사실을 처리하는 것은 사용자 코드의 책임입니다.


1
투표 (위 또는 아래)를 멀리하지만 확신이 없습니다. 제공 한 따옴표는 "value" 입니다. 내 이해는 비교가 각면에 하나씩 두 값 사이에 있다는 것입니다. 각 변의 값을 두 번 이상 인스턴스화하지 마십시오. 또한 표준 (적어도 92 인용)은 결정적이지 않은 모든 기능을 언급하지는 않습니다. 귀하와 유사한 추론을 통해 표준을 준수하는 SQL 제품이 비 결정적 기능을 제공하지 않고 표준에 언급 된 기능 만 제공한다고 가정 할 수 있습니다.
ypercubeᵀᴹ

의견을 보내 주셔서 감사합니다! 나는 당신의 해석이 확실히 유효하다고 생각합니다. 내가 그 문서를 읽은 것은 이번이 처음입니다. "행 값 생성자"로 표현 된 값의 맥락에서 값을 언급하고 있는데, 문서의 다른 곳에서는 스칼라 하위 쿼리가 될 수 있습니다. 스칼라 하위 쿼리는 특히 결정적이지 않은 것처럼 보입니다. 하지만 난 내가 무슨 말을하는지 모르겠다. =)
Josh Darnell
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.