Exists 1 또는 Exists *를 사용하는 하위 쿼리


89

내 EXISTS 수표를 다음과 같이 작성했습니다.

IF EXISTS (SELECT * FROM TABLE WHERE Columns=@Filters)
BEGIN
   UPDATE TABLE SET ColumnsX=ValuesX WHERE Where Columns=@Filters
END

DBA에의 전생에서의 하나는 내가 할 때 저에게 EXISTS절을 사용하는 SELECT 1대신SELECT *

IF EXISTS (SELECT 1 FROM TABLE WHERE Columns=@Filters)
BEGIN
   UPDATE TABLE SET ColumnsX=ValuesX WHERE Columns=@Filters
END

이것이 정말로 차이를 만들까요?


1
EXISTS (SELECT NULL FROM ...)를 잊었습니다. 이것은 최근 btw에 질문했다
OMG Ponies

17
추신은 새로운 DBA를 얻습니다. 미신은 IT, 특히 데이터베이스 관리에서 자리를 차지하지 않습니다 (이전 DBA의 !!!)
Matt Rogish

답변:


136

아니요, SQL Server는 똑똑하고 EXISTS에 사용되고 있음을 알고 시스템에 데이터를 반환하지 않습니다.

Quoth Microsoft : http://technet.microsoft.com/en-us/library/ms189259.aspx?ppud=4

EXISTS에 의해 도입 된 하위 쿼리의 선택 목록은 거의 항상 별표 (*)로 구성됩니다. 하위 쿼리에 지정된 조건을 충족하는 행이 존재하는지 테스트하는 것이므로 열 이름을 나열 할 이유가 없습니다.

자신을 확인하려면 다음을 실행하십시오.

SELECT whatever
  FROM yourtable
 WHERE EXISTS( SELECT 1/0
                 FROM someothertable 
                WHERE a_valid_clause )

실제로 SELECT 목록으로 무언가를 수행하는 경우 div 0 오류가 발생합니다. 그렇지 않습니다.

편집 : 참고, SQL 표준은 실제로 이것에 대해 이야기합니다.

ANSI SQL 1992 표준, 191 페이지 http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt

3) 케이스 :
인 경우] a) <select list>"*"단순히 포함되는 <subquery> 즉시에 포함되어 그 <exists predicate>후,이 <select list> (A)에 상당 <value expression> 임의하다 <literal>.


1
EXISTS1/0 의 트릭은 이것까지 확장 될 수 있습니다 SELECT 1 WHERE EXISTS(SELECT 1/0). 두 번째 SELECTFROM절이 없기 때문에 한 단계 더 추상적으로 보입니다
whytheq

1
@whytheq-또는 SELECT COUNT(*) WHERE EXISTS(SELECT 1/0). SQL Server에 SELECT없는 A FROM는 단일 행 테이블에 액세스하는 것처럼 처리됩니다 (예 : dual다른 RDBMS 의 테이블에서 선택하는 것과 유사 )
Martin Smith

@MartinSmith 건배-그래서 요점은 1 행 테이블이 여전히 쓰레기 SELECT이지만 다른 일을하기 전에 1 행 테이블 을 생성 한다는 것입니다 . 1/0EXISTS
whytheq

항상 그렇습니까? 아니면 특정 버전의 SQL Server에 도입 된 최적화입니까?
Martin Brown

1
@MartinSmith TIL "quoth". 다시 고쳐 주셔서 감사합니다.
Gurwinder Singh

113

이 오해의 이유는 아마도 모든 칼럼을 읽게 될 것이라는 믿음 때문일 ​​것입니다. 이것이 사실이 아님을 쉽게 알 수 있습니다.

CREATE TABLE T
(
X INT PRIMARY KEY,
Y INT,
Z CHAR(8000)
)

CREATE NONCLUSTERED INDEX NarrowIndex ON T(Y)

IF EXISTS (SELECT * FROM T)
    PRINT 'Y'

계획 제공

계획

이는 인덱스에 모든 열이 포함되어 있지 않음에도 불구하고 SQL Server에서 사용 가능한 가장 좁은 인덱스를 사용하여 결과를 확인할 수 있음을 보여줍니다. 인덱스 액세스는 세미 조인 연산자 아래에 있으며 이는 첫 번째 행이 반환되는 즉시 스캔을 중지 할 수 있음을 의미합니다.

따라서 위의 믿음이 잘못된 것이 분명합니다.

그러나 쿼리 최적화 팀의 코너 커닝햄 설명 여기에 그가 일반적으로 사용하는 SELECT 1이 작은 성능 차이를 만들 수 있습니다이 경우에 컴파일에서 쿼리를.

QP는 *파이프 라인의 초기에 모든 의 항목 을 가져와 확장 하고 객체 (이 경우 열 목록)에 바인딩합니다. 그런 다음 쿼리의 특성으로 인해 불필요한 열을 제거합니다.

따라서 EXISTS다음과 같은 간단한 하위 쿼리의 경우 :

SELECT col1 FROM MyTable WHERE EXISTS (SELECT * FROM Table2 WHERE MyTable.col1=Table2.col2)*잠재적으로 큰 열 목록으로 확장 된 다음의 의미 체계 EXISTS에 해당 열이 필요하지 않다고 판단 되므로 기본적으로 모두 제거 할 수 있습니다.

" SELECT 1"는 쿼리 컴파일 중에 해당 테이블에 대해 불필요한 메타 데이터를 검사하지 않아도됩니다.

그러나 런타임시 쿼리의 두 가지 형식은 동일하며 동일한 런타임을 갖습니다.

여러 열이있는 빈 테이블에서이 쿼리를 표현하는 네 가지 가능한 방법을 테스트했습니다. SELECT 1SELECT *SELECT Primary_KeySELECT Other_Not_Null_Column.

OPTION (RECOMPILE)초당 평균 실행 수를 사용하여 루프에서 쿼리를 실행 하고 측정했습니다. 아래 결과

여기에 이미지 설명 입력

+-------------+----------+---------+---------+--------------+
| Num of Cols |    *     |    1    |   PK    | Not Null col |
+-------------+----------+---------+---------+--------------+
| 2           | 2043.5   | 2043.25 | 2073.5  | 2067.5       |
| 4           | 2038.75  | 2041.25 | 2067.5  | 2067.5       |
| 8           | 2015.75  | 2017    | 2059.75 | 2059         |
| 16          | 2005.75  | 2005.25 | 2025.25 | 2035.75      |
| 32          | 1963.25  | 1967.25 | 2001.25 | 1992.75      |
| 64          | 1903     | 1904    | 1936.25 | 1939.75      |
| 128         | 1778.75  | 1779.75 | 1799    | 1806.75      |
| 256         | 1530.75  | 1526.5  | 1542.75 | 1541.25      |
| 512         | 1195     | 1189.75 | 1203.75 | 1198.5       |
| 1024        | 694.75   | 697     | 699     | 699.25       |
+-------------+----------+---------+---------+--------------+
| Total       | 17169.25 | 17171   | 17408   | 17408        |
+-------------+----------+---------+---------+--------------+

로가 일관된 사이의 승자입니다 볼 수 있습니다 SELECT 1및이 SELECT *와 둘 사이의 차이는 무시할 접근한다. SELECT Not Null col과는 SELECT PK약간 빠른하지만 표시 않습니다.

테이블의 열 수가 증가함에 따라 네 개의 쿼리 모두 성능이 저하됩니다.

테이블이 비어 있으므로이 관계는 열 메타 데이터의 양으로 만 설명 할 수 있습니다. 들어 COUNT(1)는이가 다시 작성됩니다 것을 쉽게 알 수있다 COUNT(*)아래에서 그 과정에서 어떤 점에서.

SET SHOWPLAN_TEXT ON;

GO

SELECT COUNT(1)
FROM master..spt_values

다음 계획을 제공합니다

  |--Compute Scalar(DEFINE:([Expr1003]=CONVERT_IMPLICIT(int,[Expr1004],0)))
       |--Stream Aggregate(DEFINE:([Expr1004]=Count(*)))
            |--Index Scan(OBJECT:([master].[dbo].[spt_values].[ix2_spt_values_nu_nc]))

디버거를 SQL Server 프로세스에 연결하고 아래를 실행하는 동안 임의로 중단

DECLARE @V int 

WHILE (1=1)
    SELECT @V=1 WHERE EXISTS (SELECT 1 FROM ##T) OPTION(RECOMPILE)

테이블에 1,024 개의 열이있는 경우 대부분의 경우 호출 스택이 아래와 같이 표시되어 SELECT 1사용되는 경우에도 열 메타 데이터를로드하는 데 실제로 많은 시간을 소비하고 있음을 알 수 있습니다 . 테이블에 무작위로 1 개의 열이 있습니다. 10 번의 시도에서이 호출 스택에 도달하지 않았습니다.)

sqlservr.exe!CMEDAccess::GetProxyBaseIntnl()  - 0x1e2c79 bytes  
sqlservr.exe!CMEDProxyRelation::GetColumn()  + 0x57 bytes   
sqlservr.exe!CAlgTableMetadata::LoadColumns()  + 0x256 bytes    
sqlservr.exe!CAlgTableMetadata::Bind()  + 0x15c bytes   
sqlservr.exe!CRelOp_Get::BindTree()  + 0x98 bytes   
sqlservr.exe!COptExpr::BindTree()  + 0x58 bytes 
sqlservr.exe!CRelOp_FromList::BindTree()  + 0x5c bytes  
sqlservr.exe!COptExpr::BindTree()  + 0x58 bytes 
sqlservr.exe!CRelOp_QuerySpec::BindTree()  + 0xbe bytes 
sqlservr.exe!COptExpr::BindTree()  + 0x58 bytes 
sqlservr.exe!CScaOp_Exists::BindScalarTree()  + 0x72 bytes  
... Lines omitted ...
msvcr80.dll!_threadstartex(void * ptd=0x0031d888)  Line 326 + 0x5 bytes C
kernel32.dll!_BaseThreadStart@8()  + 0x37 bytes 

이 수동 프로파일 링 시도는 VS 2012 코드 프로파일 러에 의해 백업되며 두 경우에 컴파일 시간을 소비하는 매우 다른 함수 선택을 보여줍니다 ( 상위 15 개 함수 1024 열상위 15 개 함수 1 열 ).

SELECT 1SELECT *버전 모두 열 권한을 확인하고 사용자에게 테이블의 모든 열에 대한 액세스 권한이 부여되지 않으면 실패합니다.

에 대한 대화에서 내가 작성한 예

CREATE USER blat WITHOUT LOGIN;
GO
CREATE TABLE dbo.T
(
X INT PRIMARY KEY,
Y INT,
Z CHAR(8000)
)
GO

GRANT SELECT ON dbo.T TO blat;
DENY SELECT ON dbo.T(Z) TO blat;
GO
EXECUTE AS USER = 'blat';
GO

SELECT 1
WHERE  EXISTS (SELECT 1
               FROM   T); 
/*  ↑↑↑↑ 
Fails unexpectedly with 

The SELECT permission was denied on the column 'Z' of the 
           object 'T', database 'tempdb', schema 'dbo'.*/

GO
REVERT;
DROP USER blat
DROP TABLE T

따라서 사용할 때 사소한 명백한 차이점 SELECT some_not_null_col은 특정 열에 대한 권한 만 확인한다는 것입니다 (여전히 모두에 대한 메타 데이터를로드하지만). 그러나 이것은 기본 테이블의 열 수가 증가함에 따라 더 작아지는 경우 두 접근 방식 간의 백분율 차이로 사실과 맞지 않는 것 같습니다.

어떤 경우에도 나는 그 차이가 매우 사소하고 쿼리 컴파일 중에 만 명백하기 때문에 서두르지 않고 모든 쿼리를이 형식으로 변경하지 않을 것입니다. OPTION (RECOMPILE)후속 실행에서 캐시 된 계획을 사용할 수 있도록 제거하면 다음이 제공됩니다.

여기에 이미지 설명 입력

+-------------+-----------+------------+-----------+--------------+
| Num of Cols |     *     |     1      |    PK     | Not Null col |
+-------------+-----------+------------+-----------+--------------+
| 2           | 144933.25 | 145292     | 146029.25 | 143973.5     |
| 4           | 146084    | 146633.5   | 146018.75 | 146581.25    |
| 8           | 143145.25 | 144393.25  | 145723.5  | 144790.25    |
| 16          | 145191.75 | 145174     | 144755.5  | 146666.75    |
| 32          | 144624    | 145483.75  | 143531    | 145366.25    |
| 64          | 145459.25 | 146175.75  | 147174.25 | 146622.5     |
| 128         | 145625.75 | 143823.25  | 144132    | 144739.25    |
| 256         | 145380.75 | 147224     | 146203.25 | 147078.75    |
| 512         | 146045    | 145609.25  | 145149.25 | 144335.5     |
| 1024        | 148280    | 148076     | 145593.25 | 146534.75    |
+-------------+-----------+------------+-----------+--------------+
| Total       | 1454769   | 1457884.75 | 1454310   | 1456688.75   |
+-------------+-----------+------------+-----------+--------------+

내가 사용한 테스트 스크립트는 여기에서 찾을 수 있습니다.


3
+1이 답변은 실제 데이터를 얻기위한 노력에 대해 더 많은 찬성표를받을 가치가 있습니다.
Jon

1
이러한 통계가 생성 된 SQL Server의 버전을 아십니까?
Martin Brown

3
@MartinBrown-IIRC는 원래 2008 년에 최근 편집을 위해 2012 년에 테스트를 다시 작성했고 동일한 것을 발견했습니다.
Martin Smith

8

가장 좋은 방법은 두 버전의 성능을 테스트하고 두 버전의 실행 계획을 확인하는 것입니다. 열이 많은 테이블을 선택하십시오.


2
+1. 왜 이것이 반대표를 받았는지 모르겠습니다. 나는 항상 남자에게 물고기를주는 것보다 물고기를 가르치는 것이 낫다고 생각했습니다. 사람들은 어떻게 무언가를 배울까요?
오우거 시편 33

5

SQL Server에는 차이가 없으며 SQL Server에서는 문제가 없었습니다. 옵티마이 저는 이들이 동일하다는 것을 알고 있습니다. 실행 계획을 보면 이들이 동일하다는 것을 알 수 있습니다.


1

개인적으로 동일한 쿼리 계획에 최적화되지 않는다는 사실을 믿기가 매우 어렵습니다. 그러나 특정 상황을 알 수있는 유일한 방법은 테스트하는 것입니다. 그렇다면 다시보고하십시오!


-1

실제 차이는 아니지만 성능이 매우 작을 수 있습니다. 경험상 필요한 것보다 더 많은 데이터를 요청해서는 안됩니다.

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