점점 복잡한 쿼리를 짜다보니 SQL 최적화에 관심이 많아졌다. 인덱스든 뭐든 DB부하를 최적화하려면 기반 원리를 알아야겠다는 생각해 공부한 내용을 정리하려고 한다.
일단 SQL이 느린 이유는 무엇일까? 컴퓨터 구조 과목을 배우던 때를 기억해보자. 디스크 I/O는 느리기 때문에 컴퓨터는 메모리 혹은 레지스터를 활용하였다. DB역시 마찬가지이다. DBMS는 DB서버와 스토리지로 나뉘는데 스토리지에서 데이터를 읽어올 때 디스크 I/O 가 발생한다. I/O가 발생하면 프로세스는 대기 큐에서 sleep하고 있기 때문에 I/O가 많아지면 성능이 느려질 수 밖에 없다. 결국 본질적으로 SQL을 최적화 한다는 것은 DISK I/O를 줄이는 것을 의미한다.
데이터베이스는 다음과 같은 구조로 데이터를 저장한다.
테이블 또는 인덱스 블록을 읽는 방식으로는 시퀸셜 액세스와 랜덤 액세스가 존재한다.
이와 관련되어 데이터를 스캔하는 방식은 다음과 같다.
디스크 I/O가 SQL의 성능을 결정한다. SQL을 최적화하기 위해 DISK I/O를 줄여야 한다. DBMS에는 DB 버퍼 캐시가 있으며, 디스크에서 읽은 데이터블록을 캐싱해두어서 I/O콜을 줄일 수 있다. I/O 기준을 잡을 때 두 가지 개념이 존재한다.
SQL을 수행하면서 읽은 총 블록 I/O이다.
SQL을 수행하면서 디스크에서 발생한 총 블록 I/O를 말한다.
한 번에 한 블록 씩 요청해서 메모리에 적재하는 방식을 Single Block I/O라고 한다. 인덱스를 통한 소규모 데이터를 읽을 때 사용된다.
한번에 여러 블록 씩 요청해서 메모리에 적재하는 방식을 Multi Block I/O라고 한다. 대량으로 데이터를 읽을 때 유용하다.
일반적으로 우리는 인덱스를 쓰면 조회할 때 성능이 올라가는 것으로 알고 있다. 그 이유는 인덱스는 특정 조건에 맞는 데이터를 B+트리와 같은 자료구조를 통해 단 시간에 빠르게 찾을 수 있기 때문이다.
그러나 인덱스 컬럼의 조건에 맞는 데이터 양이 전체 데이터 양에 꽤 많은 portion을 차지하고 있다면 인덱스를 통한 Index Range Scan(랜덤 액세스)가 인덱스를 사용하지 않고 전체 테이블 블록을 읽어오는 Full Scan보다 느릴 수 있다.
Table Full Scan은 Multi Block I/O 방식으로 디스크 블록을 읽는다. 한 블록에 속한 모든 레코드를 한번에 읽어들이고, 캐시에서 못 찾으면 한 번의 IO를 통해 인접한 수십 수백개의 블록을 한번에 I/O하는 메커니즘이다.
Index Range Scan을 통한 테이블 액세스는 랜덤 액세스와 SIngle Block I/O 방식으로 디스크 블록을 읽는다. 캐시에서 블록을 못찾으면, 레코드 하나를 일기 위해 매번 잠을 자는 I/O 메커니즘이다. 즉 많은 데이터를 읽을 때 Table Full Scan 보다 불리하다. 또 읽었던 블록을 반복해서 읽기 때문에 비 효율적이다.
참고자료 : 친절한 SQL 튜닝