조인은 1:N이 기본입니다. N쪽은 없을 수도 있지만, 1쪽은 반드시 하나는 있어야합니다.
EX) N(카테고리),1(상품) / N(결재이력),1(결재)
- filter 조건이 없으면 한쪽은 무조건 full scan
- a가 1, b가 N 쪽
참고
* 중복된 컬럼도 당연히 인덱스 생성가능 * PK/UK 만 중복 불가능 * Ref => 중복값 있는 인덱스 사용하는것 * eq_ref => PK/UK 사용 * 1:N 일때 1이 driving 이면 ref * 후행테이블이 N으로 중복값이 있기 때문에 ref * N이 driving 이면 eq_ref * 후행테이블이 1으로 중복값이 없기 때문에 eq_ref
- 한명의 사원은 반드시 하나의 부서에 포함되어야 함
- 그림은 부서 테이블은 미리 만들어놓을 수 있음
- semi join 확인 gogo
- inner join
- select a.dept_cd, a.dept_nm from dept a, emp b
(조인조건)where a.dept_cd=b.dept_cd
and enter_ymd >= ‘2016-01-01’;- 5건
- (이름(이름은 안나옴),부서코드,부서명) 박가수,aaaa, 가수팀 박성우,cccc,성우팀
송성우,cccc,성우팀
정아나,dddd,아나운서팀
최아나,dddd,아나운서팀- 따옴표가 안들어가면 조건이 제대로 적용이 안됨
- 원하는 조건이 아닙니다! 입사한 사원이 있는 부서리스트를 중복없이 조회
해야합니다!- select distinct a.dept_cd, a.dept_nm from dept a, emp b
(조인조건)where a.dept_cd=b.dept_cd
and enter_ymd >= ‘2016-01-01’;- 원하는 결과가 나오긴 하지만, 비효율적입니다.(distinct를 하기 때문)
- semi join
- 다 들여다보는 힘든 작업을 하지 않겠다.
- 그게 semi join을 하는 이유
- 사원이 있는 부서리스트 한번 확인하면 그 부서에 포함되어있는 사원들은 건너뛰는 것
- meterialized : 날짜 대로 정렬하고 메모리에 올려두었다가(distinct해서) 그 임시테이블과 부서 테이블을 조인해서 결과를 냄
- materialized -> 세미조인의 방법 중에 하나임
- distinct 제거 작업이 없다는 것이 중요(distinct는 굉장히 비싼작업)
<semi join이 나온이유>
여기까지의 조인은 nested loop 에서 중요한 조인을 설명
mysql hash를 제공하지 않아서, 위 4개의 조인을 자주 사용했지만, mysql 8.0에서 hash가 들어오면서 그 방식도 알아야겠죠?
sort인지 merge인지는 옵티마이저가 결정
nested loop인데 index가 없으면 카티션 곱 -> 처음부터 끝까지 조회
실행계획에서 먼저 나오는 테이블이 드라이빙되는 테이블입니다 :)
실행계획
1:N left join
left join = inner join결과(3개)+inner join 되지않은 left(1개:c하기스기저귀)
N:1 left join(inner join으로 많이 쓰기 때문에 outer join 잘하지 않음)->옵티마이저가 알아서 변경->그래서 성능차이는 없음 알아서 Inner join으로 변경되니까
inner,outer join 모두 n의 건수 나옴
1:N 관계에서,,
1쪽 데이터만 뽑고 싶다면 select 절에 속한 컬럼을 확인하고(1쪽만 조회하는지,N쪽같이 조회하는지)
=>중복제거를 하는 게 있는 것 같다면, 세미조인을 사용해라
=>액세스 양을 줄일 수 있음
제목 옆에 덧글 갯수가 있다면,,([50])덧글 테이블을 읽어야하기 때문에 쿼리가 느려짐
=> 튜닝 : 덧글의 수를 게시물 테이블의 컬럼으로 하나 놓고 덧글 하나 올라갈때 count 자동으로 올리기
mysql version확인
select version();
옵티마이저는 조인순서를 어떻게 결정?(어떤 테이블을 먼저 드라이빙)
모든 액세스 패스의 cost를 계산해서 최소 cost의 조인 순서 결정
explain에서
ref => non clustered index 탄다는것
2개 컬럼 이상이 복합돼서 인덱스를 구성
형변환우선순위가 높은쪽으로 변형
숫자>문자
숫자+문자=>숫자
select * from cust where ordr_no= '1';
‘1’이 형변환 되므로 인덱스 스캔시 문제없음
select * from cust where ordr_status_cd = 10;
ordr_status_cd 쪽이 형변환 되므로 인덱스 활용(X)
select * from cust where ordr_ymdt = '2011-02-17';
문자형은 date/time형태로 양방향 형변환 되므로 인덱스 활용(O)
select * from cust where ordr_ymdt = cast('2011-02-01‘ as datetime);
상수쪽이 datetime으로 형변환 되므로 인덱스 활용(O)
select * from ordr where substring(ordr_ymdt , 1, 4) = '2011' and substring(ordr_ymdt , 4, 2) = '02’;
컬럼쪽에 명시적인 형변환이 일어나므로 인덱스 활용(X) / 변수쪽이 형변환이 일어나면 인덱스는 타지 않음
select a.emp_no, a.emp_nm
from emp a
where a.dept_cd ='DDDD'
order by enter_ymd;
idx1(dept_cd) 와 idx2(dept_cd,enter_ymd) 는 다른 인덱스
최종 결론 = idx1(dept_cd), idx2(enter_ymd) 혹은 idx2(enter_ynd,dept_cd) 생성
다음은 실행 플랜의 Extra 필드에 나오는 메시지입니다. 각 메시지가 어떤 의미를 가지고 있는지, 또한 쿼리 성능에는 어떤 영향을 미치는지 생각해 보세요.
a) using index : index를 사용한다는 뜻 (좋음)
b) using filesort : 물리적인 정렬작업 (나쁨)->ex>order by(인덱스사용x)
c) not exists : 해당하지 않는것 / left join 조건을 만족하는 하나의 레코드를 찾았을 때 다른 레코드의 조합은 더 이상 검사하지 않는다.
select * from reserve where reserve_state_code = 'RETURN_COMPLETE’;
정답 ) alter table reserve add index idx1(reserve_state_code);
select * from product where product_class_code = 'CONCERT' order by register_datetime;
정답 1 ) alter table product add index idx1(product_class_code,register_datetime);
정답 2) product_id 는 auto increment 등록되어있고, register_datetime은 등록했을 시간이기때문에 둘의 순서가 동일함
-> product_class_code만 인덱스 걸어주고
-> select * from product where product_class_code = 'CONCERT' order by product_id;
// 이렇게 적어주어도 동일한 결과가 나옴(커버링 인덱스(쿼리를 충족하는데 필요한 모든 데이터를 갖는 인덱스)를 쓸 수 있음)
select * from product where register_datetime < '2015-02-25 00:00:00’;
정답 1) alter table product add index idx2(register_datetime);
정답 2) idx2 생성하지 않았다고 가정
-> 코드 분포도 확인하면 concert,exhibion 존재
-> 2번에서 idx1(product_class_code,register_datetime); 걸어줬음
-> select * from product where product_class_code in ('CONCERT’,’EXHIBITION’) and register_datetime < '2015-02-25 00:00:00’;
// 이렇게 적어주어도 동일한 결과가 나옴 ( idx1 사용 )
select * from product where register_datetime = '2015-02-25' order by product_class_code ;
update product set register_ymd=date_format(register_datetime, '%y%m%d');
정답 ) alter table product add index idx3(register_ymd,product_class_code);
select * from product a, hall b where a.hall_id = b.hall_id and b.register_datetime > '2016-07-01’;
시행착오)
why???)
정답) alter table product modify hall_id int not null;
-> hall(b)이 선행테이블이므로 b.hall_id가 기준이되므로 a.hall_id를 int로 변경해야함
(단, 응모시마다 숫자는 리프레쉬)
update event_entry set entry_count = entry_count+1 where evend_id = 123;
index idx1(event_id)
정답)
-> 추첨을 통해 포르쉐를 주는 것이기때문에, 몇번째 응모자인지 중요하지 않음
-> 응용서버의 메모리 같은 곳에 저장해놓고 db서버의 부하를 줄이자
-> 페이지 자체를 기획해야하는 수준
-> 화면단에서 부하를 어떻게 분산하는지도 중요하다!
select ticket_no,
reserve_no,
reserve_detail_no,
issue_state_code,
issue_datetime,
register_datetime
from ticket
where issue_state_code = ‘USE’
order by register_datetime desc;
정답) 시작점이 정해져있지 않아서(default value),
예매한지 한달 정도의 내역을 보여주고 싶은데,
몇십년전까지의 데이터를 가지고 오고 그래서 장애가 났음.
- 자동완성기능을 구현하고자 할때,
전체 항목값을 가져오고, 이를 따로 저장, 자동완성 기능 실행(한번+속도느림)
vs
사용자 입력 시마다 쿼리문 호출해서 자동완성 기능 실행(여러번+속도빠름+잦은db접근)
입력때마다 db접근<한번에 가지고 오는 것<메모리에 저장해놓는것(어플리케이션)
db에 자주 접근하는 것이 좋지 않기때문에, 처음 한번만 db에 접근해 데이터를 가지고 오고 프론트단에서 문자열 비교로 자동완성 기능 구현
- 다중조건 검색 시, 조건값이 빠질때에도 검색이되는 형태여야합니다. 어떻게 구현 가능? -> ppt 60.61.62 참고 (Dynamic query)
https://mozi.tistory.com/168