diff --git a/real-my-sql/fromitive/ch-09.md b/real-my-sql/fromitive/ch-09.md new file mode 100644 index 0000000..69fb705 --- /dev/null +++ b/real-my-sql/fromitive/ch-09.md @@ -0,0 +1,154 @@ + +쿼리 실행계획 : `EXPLAIN [쿼리문]` 을 입력하면 확인가능 +쿼리 최적화 정보 보기 : `information_schema.optimizer_trace 테이블 확인` + +쿼리 실행 과정 + +1. 쿼리를 잘개 쪼개서 파싱 +2. 파싱한 정보를 토대로 어떤 인덱스와 테이블을 읽을지 계획 +3. 계획한 정보를 통해 스토리지 엔진에서 데이터를 읽는다 + +``` java +ParseTree tree = sqlParser.parse(sqlQuery); + +// 연산 단순화.. +// 테이블 조회 순서 +// 인덱스 선택 +// 임시 테이블 가공 여부 결정 +ExecutionPlan plan = optimizer.read(tree); +``` + +옵티마이징 방식 + +1. 비용 기반 최적화(MySQL 채택) + - 여러 순서를 계산하여 비용응 최소화 하는 방향으로 최적화 +2. 규칙 기반 최적화 + - 옵티마이저가 정한 규칙대로 최적화 + +쿼리 동작 원리 + +테이블 풀 스캔 원리 + +1. 레코드 건수가 인덱스를 읽는 것 보다 작은 경우 +2. ON에 인덱스를 활용할 수 있는 적절한 조건이 없는 경우 +3. 레인지 스캔을 사용해도 비용상 별 차이 없는 경우 + +리드 어 헤드 : 데이터가 앞으로 더 필요해질 때 백그라운드에서 미리 읽어서 버퍼 풀에 저장하는 것. `innodb_read_ahead_threashold` 값을 통해 페이지 양을 설정할 수 있음 + +--- + +병렬 처리 원리 +`innodb_parallel_read_threads` 한 개의 커리를 몇개의 쓰레드를 이용해서 처리할지 결정 + +9.2.3 Order By 처리 원리 + +1. 인덱스 이용 +2. Filesort이용 (확인 방법은 extra 컬럼에 filesort 여부 체크) - 인덱스로 이용하지 않고 실행되었다면 filesort로 뜬다고 함 + +Order by 할 때 모든 정렬을 인덱스를 이용하도록 튜닝하기 어려운 이유 +``` +1. 정렬 기준이 많아서 요건별로 인덱스를 생성하는 것이 힘들때 +2. GROUP BY DISTINCT처리결과를 정렬해야 하는 경우 +3. 임시테이블의 결과를 다시 정렬해야 하는 경우 +4. 랜덤하게 결과 레코드를 가져와야 하는 경우 +``` + +9.2.3.1 소트 버퍼 + +정렬을 수행하기 위한 별도의 메모리 공간을 의미. 버퍼의 크기는 정렬해야 할 레코드의 수에 따라 가변적이지만 `sort_buffer_size` 시스템 변수를 이용해 최대 소트 버퍼의 크기를 제어할 수 있음. + +**소트 버퍼의 크기보다 큰 레코드를 정렬해야 할 경우** + +정렬해야할 레코드의 용량이 소트 버퍼를 초과하게 되면 디스크에 임시저장 한 다음 병합하는 형태로 처리함 이 병합 작업을 멀티 머지(Multi-merge)라고 한다 수행된 멀티 머지 횟수는 `sort_message_passes`에 누적되어 집계된다. + +**소트 버퍼의 최적 조건** + +256KB에서 8MB사이에서 최적의 성능을 보였다. 하지만 무조건 크다고 해서 처리속도가 빨라지진 않는다. 소트 버퍼는 각 클라이언트 세션 마다 할당이 되기 때문에 크게 설정할 때 클라이언트 접속량이 많아지면 메모리 부족 현상을 겪을 가능성이 있다. + +메모리 부족 현상을 겪으면 OOM-killer가 MySQL을 종료시킬 수 있기 때문에 소트 버퍼를 무조건 크게 증가시는 방법은 좋은 전략이 아니다. + +**9.2.3.2 정렬 알고리즘** + +**레코드 전체를 소트 버퍼에 담을 지 정렬 기준 칼럼만 소트 버퍼에 담을지**를 나눌 수 있다. + +9.2.3.2.1 싱글 패스 + +select 대상이 되는 컬럼을 전부 담아서 정렬을 수행하는 방식 + +9.2.3.2.1 투 패스 + +정렬 대상 칼럼과 프라이머리 키 값만 소트 버퍼에 담아서 정렬을 수행 정렬을 수행한 후 추가로 필요한 컬럼 정보를 조회하여 쿼리 결과를 반환한다. + +싱글 패스 vs 투 패스 : MySQL 8.0 는 일반적으로 싱글 패스 정렬 방식을 주로 사용함 하지만 아래의 조건에 따라 투 패스를 사용하기도 한다. + +* 레코드의 크기가 `max_length_for_sort_data`시스템 변수에 설정된 값 보다 클 때 +* BLOB 이나 TEXT 칼럼이 SELECT 대상에 포함 될 때 + +교훈 : 꼭 필요한 칼럼만 조회하는 것이 싱글 패스를 사용하기 때문에 더 효율적으로 가져오기 때문에 `*`는 지양하자. + +9.2.3.3 정렬 처리 방법 + +Order by가 처리될 때 아래의 3가지 경우 중 하나로 정렬이 처리된다. + +1. 인덱스를 사용한 정렬 : extra column에 아무 것도 안보임 +2. 조인에서 드라이빙 테이블만 정렬 : Using filesort +3. 조인에서 조인 결과를 임시 테이블로 저장 후 정렬 : Using temporary; Using filesort + +인덱스를 사용할 수 없을 경우 정렬 대상 레코드를 최소화 하기 위해 2가지 방법 중 하나를 사용한다. + +1. 조인의 드라이빙 테이블만 정렬한 다음 조인 수행 +2. 조인이 끝나고 일치하는 레코드를 모두 가져온 후 정렬 수행 + +9.2.3.3.1 인덱스를 이용한 정렬 + +인덱스가 B-Tree 키 값으로 로 저장되어있기 때문에 정렬이 된다. +조인이 걸린 쿼리를 실행할 경우 조인이 네스티드-루프 방식으로 실행되기 때문에 조인 때문에 드라이빙 테이블의 인덱스 읽기 순서가 흐트러지지 않는다. + +9.2.3.3.2 조인의 드라이빙 테이블만 정렬 + +조인이 있는 테이블을 정렬할 때 드라이빙 테이블 만 order by 하면 성능을 개선시킬 수 있다. + +```sql +select * from employees e, salaries s +where s.emp_no=e.emp_no and e.emp_no between 10002 and 100010 order by e.last_name; +``` + +실행 순서 + +1. e.emp_no 의 9건을 추출 +2. last_name 정렬 +3. 정렬된 결과를 순서대로 읽으면서 조인을 수행함 + +9.2.3.3.3 임시 테이블을 이용한 정렬 + +2개 이상의 테이블을 조인하면서 정렬해야 한다면 임시 테이블이 필요하다. + +```sql +select * from employees e, salaries s +where s.emp_no=e.emp_no and e.emp_no between 10002 and 100010 order by s.salary; // 이부분만 바뀌었을 때 어떻게 정렬이 되나? +``` + +정렬 할 때 기준이 드리븐 테이블에 있기 때문에 어쩔 수 없이 조인한 테이블을 저장하기 위한 임시 테이블을 생성한 후 정렬이 진행된다. + +9.2.3.3.4 정렬 처리 방법의 성능 비교 + +ORDER BY와 GROUP BY 작업을 WHERE 절을 만족하는 레코드를 LIMIT 건수 만큼만 가져와서 처리할 수 없다. 왜냐하면 조건을 만족하는 레코드를 모두 가져와서 정렬을 수행하거나 그루핑을 해야 비로소 LIMIT로 제한할 수 있기 때문이다. + +이런 경우엔 스트리밍 방식 혹은 버퍼링 방식에 따라 처리 된다. + +9.2.3.3.4.1 스트리밍 방식 + +조건이 만족할 때마다 레코드를 전달하는 방식 + +9.2.3.3.4.2 버퍼링 방식 + +정렬에 필요한 모든 레코드를 가져오고 한번에 처리하는 방식. 정렬 레코드가 많을 수록 사용자 응답이 느려진다. + +9.2.4 GROUP BY 처리 + +인덱스를 활용하지 못하면 임시 테이블을 사용하여 처리한다. + +9.2.4.1 인덱스 스캔을 이용하는 GROUP BY(타이트 인덱스 스캔) + + +