2017.07.28

더 빠른 SQL 쿼리를 위한 21가지 데이터베이스 튜닝 규칙

Sean McCown | InfoWorld


16. GUID에 대한 클러스터링을 피하라
테이블 데이터 정렬을 위해 GUID(Globally Unique Identifier: 범용 고유 식별자)를 사용하지 말라. 임의로 생성되는 이런 16비트 숫자는 사용자의 테이블을 훨씬 더 빨리 파편화한다. DATE나 IDENTIFY 같은 값을 점진적으로 증가시켜서 데이터를 정렬하는 것이 훨씬 낫다. 휘발성 있는 모든 열에 대해서도 갖은 규칙이 적용된다. 단 몇 분 만에 극적으로 테이블들이 파편화될 수도 있다.

17. 테이블에 있는 모든 것을 카운트(Count)하지 말라
테이블에 데이터가 존재하거나 어떤 고객에 대한 데이터가 존재하는 지를 확인할 필요가 있으며, 확인 결과에 따라, 어떤 조치를 취해야 한다고 가정하자.

필자는 그런 데이터의 존재를 확인하기 위해 누군가가 SELECT COUNT(*) FROM dbo.T1 명령을 실행하는 것을 자주 보았다.

SET @CT = (SELECT COUNT(*) FROM dbo.T1);
If @CT > 0
BEGIN <Do something>
END

전혀 불필요한 명령이다. 존재 여부를 확인하고 싶다면, 다음과 같이 하라:

If EXISTS (SELECT 1 FROM dbo.T1)
BEGIN
<Do something>
END

다른 말로 하면, 테이블에 있는 모든 것을 카운트하지 말라는 것이다. 첫 번째 행으로 돌아가면 찾을 수 있다. SQL 서버는 EXIST 문을 제대로 사용할 수 있을 정도로 똑똑하며, 두 번째 블록의 코드는 아주 빠르게 결과를 돌려준다. 테이블이 크면 클수록, 더 많은 차이를 낼 것이다.

18. 행을 카운트하려면 시스템 테이블(System Table)을 사용하라
커다란 테이블의 행을 정말로 카운트할 필요가 있다면, 시스템 테이블에서 끌어 올 수 있다. ‘SELECT rows from sysindex’ 명령문은 모든 인덱스에 대한 열의 수를 알려줄 것이다.

그리고 클러스터된 인덱스가 데이터 자체를 나타내기 때문에, ‘WHERE indid = 1’을 추가하면 테이블 행을 얻을 수 있다. 그 다음에는 그냥 테이블 이름을 추가하기만 하면 만사형통이다. 이렇게 하면, 최종 쿼리는 다음과 같다

SELECT rows FROM sysindexes WHERE object_name(id) = ‘T1’ AND indexid = 1

19. 필요한 수의 열만 끌어오라
열을 개별적으로 나열하는 대신 모든 쿼리를 SELECT * 명령문으로만 코딩한다면 너무 쉬울 것이다. 또 다시 문제는 필요한 것보다 더 많은 데이터를 끌어 온다는 것이다. 개발자가 120개의 열과 수 백만 개의 행을 가지고 있는 테이블을 대상으로 SELECT *를 실행하고는, 겨우 3~5개만 사용하고 말았다. 그 시점에, 개발자는 필요한 것보다 훨씬 더 많은 데이터를 처리시켰을 뿐만 아니라 다른 프로세스들로부터 자원을 뺏어가기도 한 것이다.

20. 네거티브 검색(Negative Search)를 피하기 위해 쿼리를 재 작성하라
인덱스를 사용할 수 없는 쿼리를 사용해서 데이터를 행 별로 비교할 필요가 있을 때, 예를 들어 FROM Customers WHERE RegionID <> 3 같은 경우는 인덱스를 사용할 수 있도록 쿼리를 재작성하는 것이 더 낫다.

SELECT * FROM Customers WHERE RegionID < 3 UNION ALL SELECT * FROM Customers WHERE RegionID

데이터 세트가 큰 경우, 인덱스를 사용하는 것이 테이블 스캔 버전을 크게 능가하는 결과를 내 놓을 수도 있다. 물론, 더 열악한 결과를 낼 수도 있으니 구현에 앞서 시험해보라.

필자는 이 쿼리가 팁 13번(중복 처리를 피하라)을 어긴다는 것을 알았지만, 융통성 없는 규칙은 없다는 것을 보여주는 것이기도 하다. 여기서는 중복 처리를 했지만, 대가가 큰 테이블 스캔을 피하기 위해서이다.

21. 맹목적으로 코드를 재사용하지 말라
필요한 데이터를 끌어온다는 것을 알기 때문에 다른 누군가의 코드를 복사하기가 십상이다. 문제는 종종 필요한 것보다 훨씬 더 많은 데이터를 끌어오고 있으며, 개발자들이 양을 줄이려 하는 경우는 거의 없어서, 거대한 데이터 상위 집합에 이르고 만다. 이는 대개 추가적인 외부 조인(Outer Join)이나 WHERE 문에서 추가 조건 형태로 나타난다. 재사용된 코드를 꼭 필요한 수준으로 줄일 수 있다면 커다란 성능 이득을 볼 수 있다.

이런 기법들 모두가 모든 상황에서 작동하지는 않는다는 것만 명심하라. 어떤 기법이 가장 잘 동작하는지를 알기 위해 실험을 해야만 할 것이다. 그렇지만, 일반적으로 언급한 SQL 팁들을 잘 사용하면 동시성을 증가시키고, 성능을 가속화시키며, DBA부터 최종 사용자들까지, 모든 사람의 삶을 훨씬 더 쉽게 만들어 줄 것이다.  editor@itworld.co.kr



2017.07.28

더 빠른 SQL 쿼리를 위한 21가지 데이터베이스 튜닝 규칙

Sean McCown | InfoWorld


16. GUID에 대한 클러스터링을 피하라
테이블 데이터 정렬을 위해 GUID(Globally Unique Identifier: 범용 고유 식별자)를 사용하지 말라. 임의로 생성되는 이런 16비트 숫자는 사용자의 테이블을 훨씬 더 빨리 파편화한다. DATE나 IDENTIFY 같은 값을 점진적으로 증가시켜서 데이터를 정렬하는 것이 훨씬 낫다. 휘발성 있는 모든 열에 대해서도 갖은 규칙이 적용된다. 단 몇 분 만에 극적으로 테이블들이 파편화될 수도 있다.

17. 테이블에 있는 모든 것을 카운트(Count)하지 말라
테이블에 데이터가 존재하거나 어떤 고객에 대한 데이터가 존재하는 지를 확인할 필요가 있으며, 확인 결과에 따라, 어떤 조치를 취해야 한다고 가정하자.

필자는 그런 데이터의 존재를 확인하기 위해 누군가가 SELECT COUNT(*) FROM dbo.T1 명령을 실행하는 것을 자주 보았다.

SET @CT = (SELECT COUNT(*) FROM dbo.T1);
If @CT > 0
BEGIN <Do something>
END

전혀 불필요한 명령이다. 존재 여부를 확인하고 싶다면, 다음과 같이 하라:

If EXISTS (SELECT 1 FROM dbo.T1)
BEGIN
<Do something>
END

다른 말로 하면, 테이블에 있는 모든 것을 카운트하지 말라는 것이다. 첫 번째 행으로 돌아가면 찾을 수 있다. SQL 서버는 EXIST 문을 제대로 사용할 수 있을 정도로 똑똑하며, 두 번째 블록의 코드는 아주 빠르게 결과를 돌려준다. 테이블이 크면 클수록, 더 많은 차이를 낼 것이다.

18. 행을 카운트하려면 시스템 테이블(System Table)을 사용하라
커다란 테이블의 행을 정말로 카운트할 필요가 있다면, 시스템 테이블에서 끌어 올 수 있다. ‘SELECT rows from sysindex’ 명령문은 모든 인덱스에 대한 열의 수를 알려줄 것이다.

그리고 클러스터된 인덱스가 데이터 자체를 나타내기 때문에, ‘WHERE indid = 1’을 추가하면 테이블 행을 얻을 수 있다. 그 다음에는 그냥 테이블 이름을 추가하기만 하면 만사형통이다. 이렇게 하면, 최종 쿼리는 다음과 같다

SELECT rows FROM sysindexes WHERE object_name(id) = ‘T1’ AND indexid = 1

19. 필요한 수의 열만 끌어오라
열을 개별적으로 나열하는 대신 모든 쿼리를 SELECT * 명령문으로만 코딩한다면 너무 쉬울 것이다. 또 다시 문제는 필요한 것보다 더 많은 데이터를 끌어 온다는 것이다. 개발자가 120개의 열과 수 백만 개의 행을 가지고 있는 테이블을 대상으로 SELECT *를 실행하고는, 겨우 3~5개만 사용하고 말았다. 그 시점에, 개발자는 필요한 것보다 훨씬 더 많은 데이터를 처리시켰을 뿐만 아니라 다른 프로세스들로부터 자원을 뺏어가기도 한 것이다.

20. 네거티브 검색(Negative Search)를 피하기 위해 쿼리를 재 작성하라
인덱스를 사용할 수 없는 쿼리를 사용해서 데이터를 행 별로 비교할 필요가 있을 때, 예를 들어 FROM Customers WHERE RegionID <> 3 같은 경우는 인덱스를 사용할 수 있도록 쿼리를 재작성하는 것이 더 낫다.

SELECT * FROM Customers WHERE RegionID < 3 UNION ALL SELECT * FROM Customers WHERE RegionID

데이터 세트가 큰 경우, 인덱스를 사용하는 것이 테이블 스캔 버전을 크게 능가하는 결과를 내 놓을 수도 있다. 물론, 더 열악한 결과를 낼 수도 있으니 구현에 앞서 시험해보라.

필자는 이 쿼리가 팁 13번(중복 처리를 피하라)을 어긴다는 것을 알았지만, 융통성 없는 규칙은 없다는 것을 보여주는 것이기도 하다. 여기서는 중복 처리를 했지만, 대가가 큰 테이블 스캔을 피하기 위해서이다.

21. 맹목적으로 코드를 재사용하지 말라
필요한 데이터를 끌어온다는 것을 알기 때문에 다른 누군가의 코드를 복사하기가 십상이다. 문제는 종종 필요한 것보다 훨씬 더 많은 데이터를 끌어오고 있으며, 개발자들이 양을 줄이려 하는 경우는 거의 없어서, 거대한 데이터 상위 집합에 이르고 만다. 이는 대개 추가적인 외부 조인(Outer Join)이나 WHERE 문에서 추가 조건 형태로 나타난다. 재사용된 코드를 꼭 필요한 수준으로 줄일 수 있다면 커다란 성능 이득을 볼 수 있다.

이런 기법들 모두가 모든 상황에서 작동하지는 않는다는 것만 명심하라. 어떤 기법이 가장 잘 동작하는지를 알기 위해 실험을 해야만 할 것이다. 그렇지만, 일반적으로 언급한 SQL 팁들을 잘 사용하면 동시성을 증가시키고, 성능을 가속화시키며, DBA부터 최종 사용자들까지, 모든 사람의 삶을 훨씬 더 쉽게 만들어 줄 것이다.  editor@itworld.co.kr



X