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). 두 번째 SELECT는 FROM절이 없기 때문에 한 단계 더 추상적으로 보입니다
— 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 1대 SELECT *대 SELECT Primary_Key대 SELECT 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 1및 SELECT *버전 모두 열 권한을 확인하고 사용자에게 테이블의 모든 열에 대한 액세스 권한이 부여되지 않으면 실패합니다.

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

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.