풀 테이블 스캔 : 인덱스를 사용하지 않고 테이블의 데이터를 처음부터 끝까지 읽어서 작업을 처리
풀 테이블 스캔을 선택하는 조건
너무 작아
, 인덱스를 읽는 것 보다 풀 테이블 스캔이 빠른 경우너무 많은
경우풀 테이블 스캔은 많은 디스크 읽기가 필요하므로, 한꺼번에 여러 개의 블록이나 페이지를 읽어오는 기능
을 내장하고 있다.
다만, 한꺼번에 몇 개씩 페이지를 읽어올지 설정하는 시스템 변수는 없다.
→ InnoDB는 한 페이지씩 읽어 오는게 아님.
InnoDB 스토리지 엔진은 특정 테이블의 연속된 데이터 페이지가 읽히면
백그라운드 스레드에 의해 리드 어헤드
작업이 자동으로 시작됨.
리드 어헤드 : 어떤 영역의 데이터가 앞으로 필요해지리라는 것을 미리 예측
해 미리 읽어 버퍼 풀
에 가져다 두는 것
풀 테이블 스캔 실행
→ 몇 개의 페이지는 포그라운드 스레드
→ 특정 시점 이후에는 백그라운드 스레드에 의해 가져옴.
한 번에 4개 또는 8개, 최대 64개의 데이터 페이지
다음 쿼리는 풀 테이블 스캔을 할 것 같지만, 풀 인덱스 스캔을 하게 될 가능성이 높다.
단순히 레코드의 건수만 필요로 하는 쿼리라면 용량이 작은 인덱스를 선택하는 것이 I/O 작업을 줄일 수 있기 때문
> SELECT COUNT(*) FROM employess;
병렬 처리 : 하나의 쿼리
를 여러 스레드
가 작업을 동시에 나누어 처리
→ 여러 스레드가 동시에 각각의 쿼리를 처리하는 것은 MySQL 서버가 처음 만들어질 때 부터 가능.
MySQL 8.0에서는 innodb_parallel_read_threads 라는 시스템 변수로 하나의 쿼리를 최대 몇 개의 스레드를 이용해서 처리할지 변경할 수 있다.
병렬 처리용 스레드 개수를 아무리 늘리더라도 서버에 장착된 CPU의 코어 개수를 넘어서는 경우에는 오히려 성능이 떨어질 수 있으니 주의하자.
→ CONTEXT SWITCHING
1. Index
장점
이미 인덱스가 정렬
돼 있어 순서대로 읽기만 하면 되므로 매우 빠름.
→ 너 오타 아니냐 ? SELECT 아니냐 ?
단점
2. Filesort
장점
단점
모든 정렬을 인덱스를 이요하도록 튜닝하기란 거의 불가능하다.
Filesort를 이용했을 경우, 실행 계획에 Using filesort
라고 나옴.
소트 버퍼
sort_buffer_size
라는 시스템 변수로 설정→ 소트 버퍼에서 정렬을 수행하고, 그 결과를 임시로 디스크에 기록
하고, 다시 가져오기 때문에 많은 I/O 작업이 필요하다.
💡 소트 버퍼를 너무 크게 설정하면 서버의 메모리가 부족해져서 MySQL 서버가 종료될 수 있다.
대량 데이터의 정렬이 필요한 경우 해당 세션의 소트 버퍼만 일시적으로 늘려서 쿼리를 실행하고 다시 줄이는 것도 굿 !
정렬 알고리즘
std::stable_sort
→ Quick-sort, Heap-sort를 복합적으로 사용
싱글 패스 정렬 방식
mysql > SELECT emp_no, first_name, last_name
FROM employees
ORDER BY first_name;
정렬에 필요 없는 last_name도 소트 버퍼에 넣고, 멀티 머지 후에 결과를 반환한다.
투 패스 정렬 방식
정렬된 순서대로 다시 PK로 테이블을 읽어
SELECT할 칼럼을 가져온다.싱글 패스 정렬보다 오래된 기법임.
→ 투 패스 방식은 테이블을 두 번 읽어야 하기 때문에 상당히 불합리하지만, 새로운 정렬 방식인 싱글패스는 한 번만 읽어도 된다.
투 패스 정렬 방식을 사용하는 경우
💡 SELECT 쿼리에서 꼭 필요한 칼럼만 조회하지 않고, 모든 칼럼(*)을 가져오도록 개발할 때가 많다.
하지만 이는 정렬 버퍼를 몇 배에서 몇십 배까지 비효율적으로 사용할 가능성이 크다.
불필요한 칼럼을 SELECT하지 않게 쿼리를 작성하는 것이 효율적이다.
인덱스를 이용한 정렬
조인의 드라이빙 테이블만 정렬
mysql > SELECT * FROM employees e, salaries s where s.emp_no=ememp_no and e.emp_no between ~ order by e.last_name;
last_name은 employees 테이블의 PK와 전혀 연관이 없으므로 인덱스를 이용한 정렬은 불가능하다.
→ ORDER BY 절의 정렬 기준 칼럼이 드라이빙 테이블에 포함된 칼럼임을 알 수 있다.
→ 옵티마이저는 드라이빙 테이블만 검색해서 정렬을 먼저 수행하고, 그 결과와 salaries 테이블을 조인한 것이다.
드리븐 테이블의 order by 조건은, 조인된 데이터를 가지고 정렬할 수 밖에 없다.
정렬 처리 방법의 성능 비교
쿼리가 처리되는 방법
1. 스트리밍 방식
쿼리가 얼마나 많은 레코드를 조회하느냐에 상관없이 빠른 응답 시간을 보장
2. 버퍼링 방식
JDBC 라이브러리가 버퍼링하는 것.
JDBC를 사용하지 않는 SQL 클라이언트 도구는 이러한 버퍼링을 하지 않기 때문에 아무리 큰 테이블이라고 하더라도 첫 번째 레코드는 매우 빨리 가져온다.
→ 불필요한 네트워크 요청을 최소화되기 때문에 전체 처리량이 뛰어나서 이렇게 작업한다.
→ 아주 대량의 데이터를 가져와야 할 때는 스트리밍 방식으로 변경할 수 있다.
인덱스를 사용하지 못하고 Filesort 작업을 거쳐야 하는 쿼리에서 LIMIT 조건이 아무런 도움이 되지 못하는 것은 아니다.
LIMIT 10이 있다면, 1000건의 레코드를 모두 정렬하는 것이 아니라 필요한 순서대로 정렬해서 상위 10건만 정렬이 채워지면 정렬을 멈추고 결과를 반환한다.
MySQL 서버는 정렬을 위해 퀵 소트와 힙 소트 알고리즘을 사용한다.
→ 이는 LIMIT 10을 만족하는 상위 10건을 정렬하기 위해 더 많은 작업이 필요할 수도 있음을 의미한다.
인덱스를 사용할 때
인덱스를 사용하지 못할 때
인덱스 스캔을 이용하는 GROUP BY(타이트 인덱스 스캔)
그룹값
을 처리해야 해서 임시 테이블이 필요할 때도 있다.루스 인덱스 스캔을 이용하는 GROUP BY
프리픽스 인덱스는 루스 인덱스 스캔을 사용할 수 없다.
인덱스 레인지 스캔은 유니크한 값의 수가 많을수록 성능이 향상되지만, 루스 인덱스 스캔은 유니크한 값의 수가 적을수록 성능이 향상된다.
다음과 같이 인덱스가 없는 컬럼에 COUNT를 하면 임시 테이블이 2개가 필요하다.
mysql > SELECT COUNT(DISTINCT s.salary),
COUNT(DISTINCT e.last_name)
FROM employees e, salaries s
WHERE e.emp_no=s.emp_no
AND e.emp_no BETWEEN
그러나, 인덱스가 있는 칼럼에 대해 DISTINCT 처리를 수행할 때는 인덱스를 풀 스캔하거나 레인지 스캔하면서 임시 테이블 없이 최적화된 처리를 수행할 수 있다.
내부 임시 테이블 활용
RealMySQL 8.0 - Ch.9