Ch11. 쿼리 작성 및 최적화 - 2
Opened this issue · 5 comments
148p insert 전 까지
-
LIMIT
78p 3번째 쿼리는 카디널리티가 10보다 작으면 모든 row 를 읽어야 한다.
4번째 쿼리는 일부만 정렬한다는 것에 동의하지 않는다. 정확하게는 WHERE 절에 해당하는 row 를 조회한 후, 정렬을 수행한 다음에 LIMIT 을 실행할 것이다. -
페이징
WHERE salary >= 38864 AND NOT (salary=38864 AND emp_no <= 274049)
위 조건에서 NOT 을 풀어쓰면 다음과 같다.
WHERE salary >= 38864 AND (salary !=38864 OR emp_no > 274049)
이거 index 사용하는지 확인 해야함. -
OUTER JOIN
기본적으로 WHERE 조건에 OUTER JOIN이 되는 테이블에 대한 조건이 들어가면 INNER JOIN 으로 최적화 한다. (조건이 참이 되려면 null 인 경우를 제외 -> JOIN 이 되는 row 만 확인 -> INNER JOIN)
ANTI JOIN 인 경우는 조건절로 추가 가능 -
지연된 조인
조인을 바로 한다면, 10만건 쿼리에 대해서도 JOIN 을 하게 될 수 있음 -> 주요 조건으로 먼저 LIMIT 으로 제한한 다음에 JOIN 을 LIMIT 한 만큼만 수행한다. -
WITH ROLLUP
group by 에 대한 전체 통계 기능 -
ORDER BY
https://dev.mysql.com/doc/refman/5.7/en/order-by-optimization.html
https://dev.mysql.com/doc/refman/8.0/en/order-by-optimization.html
여러 방향 정렬 (8.0 부터 가능) -
CTE
- 재귀적
- 비재귀적
CTE 임시 테이블은 재사용 가능하여 효율적일 수 있다.
-
WINDOW 함수
GROUP BY 와 비교했을 때 집계 결과만을 얻을수 있는것과 row 별로 집계 결과를 얻을 수 있는 것의 차이
이때 OVER 내에 범위를 지정할 수 있다 (ex. 해당 row 에서 1년 전까지의 집계 등) -
잠금을 사용하는 SELECT
FOR SHARE (읽기 잠금) / FOR UPDATE (쓰기 잠금)
기본적으로 잠금이 없는 SELECT 는 InnoDB 에서는 아무 제약없이 바로 조회 가능하다.
NO WAIT 와 SKIP LOCKED 로 잠금을 우회 가능하다.
72p short circuit evaluation
- WHERE 절에 AND 와 OR로 연결된 조건들에서 인덱스를 활용하여 효율적으로 데이터의 범위를 줄일 수 있는 것부터 먼저 실행하도록 판단
- 예를 들어 ix_B가 있고 SELECT * FROM table WHERE A AND B 가 있을 떄 B를 먼저 찾아보도록 판단
78p limit
- limit은 쿼리에서 필요한 레코드 건수만 준비되면 즉시 쿼리 종료
- LIMIT 20000, 10 이면 2만10건의 레코드를 읽는다
- Q1)
- 저번에 페이징 이야기하면서 뒤로갈수록 느려진다고 했고 다들 자연스럽게 수긍하는 분위기였는데
- 이것이 페이지 뒤로 갈수록 느려지는 일반적인 원인?
- 84p "물론 게시물을 가져오는 두번째 쿼리도 다음 페이지로 넘어가면 갈수록 성능이 더 느려질 가능성이 높다"
- Q2) 84p에서 이전과 다음 버튼만 표시하는 방식을 검토해보라는데 무슨 의미?
83p count()
- count()의
*
은 전체 컬럼의 레코드를 읽는게 아니라 그냥 레코드 자체를 의미 - count()의
컬럼이나 표현식
은 그 컬럼이나 표현식이 NULL이 아닌 레코드 건수만 반환
85p join 순서와 인덱스
- Q3) 어느쪽에 인덱스가 있냐에 따라서 드라이빙,드리븐 테이블이 바뀌고 풀스캔을 피한다는 말이 이해가 안감
- 예를 들어
SELECT * FROM employees e, dept_emp de WHERE e.emp_no=de.emp_no
- e.emp_no에 인덱스가 있으므로 빠르게 결과를 먼저 가져온다, 즉 드라이빙 테이블
- 그 빠르게 들고온 결과에 맞춰서 de.emp_no을 매칭하므로, 드라이븐 테이블이 된다
- 라고 생각이 드는데 왜 반대일까?
- 예를 들어
90p
- 데이터 모델링할때 테이블 간의 관계를 넣는다는 것은 외래키가 있다는 것을 무조건적으로 암시한다고 생각했는데 그게 아니었다
91p 지연된 조인
- Q4) 왜 salaries 테이블 조인 수행한 횟수가 3000 * 4 번인가? salaries 레코드 갯수만큼 찍혀야하는 거 아닌가?
99p 레코드를 컬럼으로 변환
- 한줄로 꺼내고 싶을때 저렇게하지못하고 프로그래밍적으로 map 같은데 담아서 어떻게 해결했던거 같다, 단순히 데이터분류하는 작업이니까 SQL로 하는게 책임소재가 나누어져 좋을것같다
101p order by
- order by가 없는게 자동 asc 라고 착각했다. innodb경우엔 항상 pk로 클러스터링돼 있기 때문에 기본적으로 pk 순서대로 레코드를 가져온다
100~120p 정도쯤
CTE, 윈도우 함수는 넘 어려워서 패스했습니다
Common Table Expression가 모지: VIEW같은 임시테이블. 대신 CTE는 쿼리 안에서 딱 한번 생성됨. 복잡한 쿼리에서 가독성향상. 여러번 참조 하기 위함. 재귀 쿼리( Q5) 무슨 뜻? )
윈도우 함수가 뭐지: 행과 행의 연산을 목적으로 하는 함수
146p select for update 쿠폰 예시
- 진짜 그냥 단순히 lock 걸린걸 패스만 하는것으로 엄청난 성능향상을 한거처럼 보이겠다
- Q6) 레디스를 활용한다면 어떻게 할 수 있다는 것일까?
147p 주의
- "자신의 어플리케이션이 겪는 문제의 원인을 제대로 분석하지 못하고, 애플리케이션 코드는 그대로 놔두고 MySQL 서버가 느려서 트랜잭션이 느려진다고들 한다" 뭔가 저자의 고충 같은 느낌이다 ㅋㅋ
LIMIT - 77p
- 필요한 레코드 건수만 준비되면 쿼리 즉시 종료
- GROUB BY 절이 있을 경우, 처리가 완료 되고 나서야 LIMIT 처리를 수행
- LIMIT의 인자로 표현식이나 별도의 서브쿼리를 사용할 수 없다.
- LIMIT 조건의 페이징이 처음 몇 개 페이지 조회로 끝나지 않을 가능성이 높다면
- WHERE 조건절로 읽어야 할 위치를 찾고 그 위치에서 읽는 형태의 쿼리를 사용하는 것이 좋다.
- ex)
... LIMIT 200000, 10
COUNT - 82p
- COUNT(*)의 *는 레코드 자체를 의미
- 주의. InnoDB 스토리지 엔진을 사용하는 테이블에서는 COUNT(*) 쿼리라고 해도 직접 데이터나 인덱스를 읽어야만 레코드 건수를 가져올 수 있음.
- COUNT(*) 쿼리에서는 ORDER BY 필요 없음. 제거하는 게 성능에 좋다.
JOIN - 90p
- 외래키는 조인과 아무런 연관이 없다.
- 외래키를 생성하는 주목적은 데이터의 무결성 보장
지연된 조인
- 조인이 실행되기 이전에 GROUP BY나 ORDER BY를 처리하는 방식을 의미
- LIMIT이 함께 사용된 쿼리에서 더 큰 효과를 얻을 수 있음
- 모든 조인을 지연할 수 있는 건 아니고 조건이 있음. (93p)
ORDER BY - 102p
- ORDER BY 절은 정렬할 대상 칼럼명, 표현식, _칼럼의 순번_으로 명시 할 수 있다.
- 문자열 상수가 올 경우 옵티마이저가 무시
FROM 절에 사용된 서브쿼리, 뷰 - 109p
- 서브 쿼리의 결과를 항상 임시 테이블로 저장하고 다시 임시 테이블을 익는 방식으로 처리
- MySQL 5.7부터는 서브쿼리, 뷰를 외부 쿼리로 병합하는 최적화 수행
CTE(Common Table Expression) - 114p
- SQL 문장 내에서 이름을 가지는 임시 테이블
Recursive CTE
- CTE를 이용한 재귀쿼리
- 8.0 부터 지원
윈도우 함수 - 137p
- MySQL 서버의 윈도우 함수는 8.0 버전에 처음 도입, 인덱스를 이용한 최적화 부족
- 윈도우 함수는 조회하는 현재 레코드를 기준으로 연관된 레코드 집합의 연산을 수행한다.
- GROUP BY와 달리 결과 집합을 그대로 유지
잠금을 사용한 SELECT - 139p
- 레코드를 읽으면서 강제로 잠금을 걸어두는 방식
- FOR UPDATE, FOR SHARE(
= LOCK IN SHARE
, 8.0 이전버전)
- FOR UPDATE, FOR SHARE(
- 주의할 사항
- InnoDB를 사용하는 테이블은 잠금 없는 읽기가 지원되기 때문에 잠금 상태더라도 쿼리를 아무런 대기 없이 실행할 수 있다.
- 선택적 잠금, NOWAIT & SKIP LOCKED
- 8.0부터 가능함
- SKIP LOCKED: 잠긴 레코드는 무시하고 잠금이 걸리지 않은 레코드만 가져온다.
Limit
n, m 수치 매우 커질 때 쿼리 수행 시간 높
Where 절로 읽어야할 위치 찾고 그 위치에서 10개만 읽는 형태로 쿼리 작성
Count
- count() 함수에 컬럼명이나 표현식이 인자로 사용되면 결과가 NULL이 아닌 레코드 건수만 반환한다
- 게시물 페이지 번호를 보여주는 방식보다는 이전, 다음 버튼만 표시하는 방식 검토
JOIN
- 보통은 인덱스 있는 테이블이 드리븐으로 선택
- 인덱스 없는 테이블이 드리븐이 될 경우, 드라이빙(앞) 테이블의 레코드 건수만큼 드리븐 테이블 풀스캔해야하기 때문
- Q?
- 드리븐 최적으로 읽을 수 있게 실행 계획 수립
- 두 컬럼 모두 인덱스가 없는 경우, 레코드 건수가 적은 테이블을 드라이빙 테이블로 선택하는 것이 효율적
OUTER JOIN (88p)
- 아우터 조인 -> 절대 드라이빙 테이블로 선택하지 못하기 때문에 실행 계획 이점 누릴 수 없다
SELECT 절 서브쿼리
- only 스칼라 서브쿼리
레코드를 칼럼으로 변환해서 조회
- 리대쉬 사용할 때, 많이 활용
- 어플리케이션에서 사용하려면, FE/BE 리소스를 들여서 만들어야 함
- BUT, MD나 PO 등등 데이터 분석을 위해서 데이터 뽑아내는 요청 많이 해주심 그때 필요
- 정기적으로 스냅샷 찍은 운영디비와 별개의 DB를 만들어 사용
토요일 피드백받은거
72p short circuit evaluation
- WHERE 절에 AND 와 OR로 연결된 조건들에서 인덱스를 활용하여 효율적으로 데이터의 범위를 줄일 수 있는 것부터 먼저 실행하도록 판단
- 예를 들어 ix_B가 있고 SELECT * FROM table WHERE A AND B 가 있을 떄 B를 먼저 찾아보도록 판단
78p limit
- limit은 쿼리에서 필요한 레코드 건수만 준비되면 즉시 쿼리 종료
- LIMIT 20000, 10 이면 2만10건의 레코드를 읽는다
- Q1)T
- 저번에 페이징 이야기하면서 뒤로갈수록 느려진다고 했고 다들 자연스럽게 수긍하는 분위기였는데
- 이것이 페이지 뒤로 갈수록 느려지는 일반적인 원인?
- 84p "물론 게시물을 가져오는 두번째 쿼리도 다음 페이지로 넘어가면 갈수록 성능이 더 느려질 가능성이 높다"
- Q2) 84p에서 이전과 다음 버튼만 표시하는 방식을 검토해보라는데 무슨 의미?
- 80~81p
- limit 으로 안될거같으면 where절에서 limit 0, 10
- where salary > ? and not (salary = ? and )
- 리미티 0부터 10까지 찾은담에 10개 마지막껄 이용해서 조건을 두번쨰부터 재구성하는 것이다
83p count()
- count()의
*
은 전체 컬럼의 레코드를 읽는게 아니라 그냥 레코드 자체를 의미 - count()의
컬럼이나 표현식
은 그 컬럼이나 표현식이 NULL이 아닌 레코드 건수만 반환
85p join 순서와 인덱스
-
Q3) 어느쪽에 인덱스가 있냐에 따라서 드라이빙,드리븐 테이블이 바뀌고 풀스캔을 피한다는 말이 이해가 안감
- 예를 들어
SELECT * FROM employees e, dept_emp de WHERE e.emp_no=de.emp_no
- e.emp_no에 인덱스가 있으므로 빠르게 결과를 먼저 가져온다, 즉 드라이빙 테이블
- 그 빠르게 들고온 결과에 맞춰서 de.emp_no을 매칭하므로, 드라이븐 테이블이 된다
- 라고 생각이 드는데 왜 반대일까?
- 예를 들어
-
A3) 인덱스가 있는게 드리븐 인덱스가 없는게 드라이빙
- 반대로해도 풀스캔을 해야한다
- 인덱스가 있는애를 드라이빙으로 잡으면 조인조건에만 있는거지 ㅈhere절이 아니다 where조건이면 인덱스쓸수있는데, 조인조건이기떄문에 컬럼을 인덱스 풀스캔을 할수는 있지만, 전체조회하는 걸 피할 순 업따 !
- 로우 하나에 대해서 드리븐 테입르 조건을 찾아야하는데 드리븐 테이블에 인덱스 없다면 무조건 다 찾아야한다
- 로우가 1번 부터 10만까지 있다 치자
- 값이 1번인걸 보고 조인을 해야하는데 드리븐에서 값이 1번인걸 찾아야한다, 그런데 인덱스가 없다? -> 그럼 다 찾아야지 인덱스가 없으니까
- 반대로 해보자
- 인덱스없는건 다찾아야한다 어쩄든!
- 드라이빙 테이블ㅇ르 찾는데 이것도 1만부터 10만까지 있다치자
- 1번인거에대해 드리븐 찾는데 이때는 인덱스 ㅣㅇㅆ으니까 그걸 찾으면 그걸 인덱스를 통해 가지고 올수있는것이다
- 그러니까 인덱스가 없는게 드라이빙테이블이 되고, 그 드라이빙테이블에 대해서 하나씩 전체적으로 보면서 로우를 하나씩 찾는다
- 그래서 nested-loop join 이다
- 만약에 둘다 인덱스가 없다면 블럭단위로 nested join을 하는 것이다
- for loop 두번 돈다고 한번 생각해봐라, 안에 있는 for문도 전체조회인데 그걸 줄이는게 뽀인뜨
90p
- 데이터 모델링할때 테이블 간의 관계를 넣는다는 것은 외래키가 있다는 것을 무조건적으로 암시한다고 생각했는데 그게 아니었다
91p 지연된 조인
- Q4) 왜 salaries 테이블 조인 수행한 횟수가 3000 * 4 번인가? salaries 레코드 갯수만큼 찍혀야하는 거 아닌가?
- sum(salary)를 하는데 대충 4개씩 조인 돤다
99p 레코드를 컬럼으로 변환
- 한줄로 꺼내고 싶을때 저렇게하지못하고 프로그래밍적으로 map 같은데 담아서 어떻게 해결했던거 같다, 단순히 데이터분류하는 작업이니까 SQL로 하는게 책임소재가 나누어져 좋을것같다
- k: 실제로 사용하게 되는 자원은 거의 비슷하다고 본다 그걸 단지 db냐 앱에냐 -> 그걸 잘 따져보는게 좋다
- bigint로 로우 150만건 정도 가지고오면 메모리에 올라가는 데이터가 한 1기가 이상된다,
- 이걸 어플리케이션에서 한다? 이거 실행할때마다 메모리에 1기가가 올라가고 그러개때문에 이런걸 디비에서도 효율적으로 처리할 수 있는가? 이런걸 고민해보는게 좋을 것이다,
- 잘 따져서 해야한다!
- 여러가지 상황변수 고려 - 네트워크 / 메모리 등등
101p order by
- order by가 없는게 자동 asc 라고 착각했다. innodb경우엔 항상 pk로 클러스터링돼 있기 때문에 기본적으로 pk 순서대로 레코드를 가져온다
100~120p 정도쯤
Common Table Expression가 모지: VIEW같은 임시테이블. 대신 CTE는 쿼리 안에서 딱 한번 생성됨. 복잡한 쿼리에서 가독성향상. 여러번 참조 하기 위함. 재귀 쿼리( Q5) 무슨 뜻? )
윈도우 함수가 뭐지: 행과 행의 연산을 목적으로 하는 함수
146p select for update 쿠폰 예시
- 진짜 그냥 단순히 lock 걸린걸 패스만 하는것으로 엄청난 성능향상을 한거처럼 보이겠다
- Q6) 레디스를 활용한다면 어떻게 할 수 있다는 것일까?
147p 주의
- "자신의 어플리케이션이 겪는 문제의 원인을 제대로 분석하지 못하고, 애플리케이션 코드는 그대로 놔두고 MySQL 서버가 느려서 트랜잭션이 느려진다고들 한다" 뭔가 DBA의 고충 같은 느낌이다 ㅋㅋ