Open mojh7 opened 1 year ago
... ORDER BY col LIMIT n
이라고 할 때, 정렬이 완료되지 않았다고 하더라도 상위 n건까지만 정렬되면 작업을 멈춤LIMIT(시작위치(오프셋), 가져올 레코드 수)
. ex) LIMIT 10의 경우 LIMIT 0, 10 과 동일결과 레코드의 건수를 반환하는 함수
칼럼이나 표현식을 인자로 받으며 "*"
를 사용할 수 있다
COUNT(*)
라고 해서 레코드의 모든 칼럼을 읽는 형태로 처리하지 않음COUNT(PK)
나 COUNT(1)
과 같이 사용하지 않고 COUNT(*)
라고 표현해도 동일한 처리 성능을 보임mysql> select * from member;
+----+------------+--------------------------------------------------------------+---------------+----------------------------+----------------------------+
| id | account_id | password | role | created_at | updated_at |
+----+------------+--------------------------------------------------------------+---------------+----------------------------+----------------------------+
| 1 | test1 | $2a$10$nT0gOH.JjmBi/5vMBIBOPeuk8kbHG4et1KR/3tNtsr/il.Yj0QKBm | ROLE_SELLER | 2022-05-19 16:26:42.482000 | 2022-05-19 16:26:42.482000 |
| 2 | test2 | $2a$10$4ibQ0mvSnVXoRexSe8ogL.1s7Xxb8OZl4bb9lpHgUpfBEc0NT4lba | ROLE_CUSTOMER | 2022-05-19 16:26:47.914000 | 2022-05-19 16:26:47.914000 |
| 3 | test3 | $2a$10$G.qUWO9y.m8v3a7N93oyIuZr5OzR/9LuM6mNcmUzXwL7sbOx4UqHe | ROLE_CUSTOMER | 2022-05-19 16:26:51.337000 | 2022-05-19 16:26:51.337000 |
| 4 | test4 | $2a$10$1h7PIxB8mCORT69vsg1Lve6YFzl/O4k349WrPhlN76BUB905JVQqC | ROLE_CUSTOMER | 2022-05-21 11:36:39.744000 | 2022-05-21 11:36:39.744000 |
| 5 | test55 | $2a$10$DoxFvCayK1r.vRstW7xHI.Awy7PuRvmMXU7TTzm4Rp1W5fqeDl6ua | ROLE_CUSTOMER | 2022-05-22 11:59:01.942000 | 2022-05-22 11:59:01.942000 |
+----+------------+--------------------------------------------------------------+---------------+----------------------------+----------------------------+
5 rows in set (0.09 sec)
mysql> select count(*) from member;
+----------+
| count(*) |
+----------+
| 5 |
+----------+
1 row in set (0.01 sec)
mysql> select count(id) from member;
+-----------+
| count(id) |
+-----------+
| 5 |
+-----------+
1 row in set (0.00 sec)
mysql> select count(1) from member;
+----------+
| count(1) |
+----------+
| 5 |
+----------+
1 row in set (0.00 sec)
mysql> select count(2) from member;
+----------+
| count(2) |
+----------+
| 5 |
+----------+
1 row in set (0.00 sec)
mysql> select * from coupon;
+----+-----------+---------+-------------+-----------+---------+----------------------------+----------------------------+----------------------------+----------------------------+
| id | member_id | title | description | max_count | status | start_at | end_at | created_at | updated_at |
+----+-----------+---------+-------------+-----------+---------+----------------------------+----------------------------+----------------------------+----------------------------+
| 1 | 1 | coupon1 | ?ㅻ챸1 | 0 | CREATED | 2022-05-20 22:51:01.000000 | 2022-07-21 13:30:00.000000 | 2022-05-22 13:04:56.544000 | 2022-05-22 13:04:56.544000 |
| 2 | 1 | coupon1 | ?ㅻ챸1 | 1000000 | CREATED | 2022-05-21 01:00:00.000000 | 2022-07-21 13:30:00.000000 | 2022-05-22 13:10:17.581000 | 2022-05-22 13:10:17.581000 |
| 3 | 1 | coupon1 | ?ㅻ챸1 | 2 | CREATED | 2022-06-21 01:00:00.000000 | 2022-09-21 13:30:00.000000 | 2022-06-24 07:13:21.184000 | 2022-06-24 07:13:21.184000 |
| 4 | 1 | 荑좏룿A | ?ㅻ챸A | 3000 | CREATED | 2022-06-24 00:00:00.000000 | 2022-08-24 13:30:00.000000 | 2022-06-24 07:13:55.794000 | 2022-06-24 07:13:55.794000 |
| 5 | 1 | 荑좏룿A | ?ㅻ챸A | 3000 | ENABLED | 2022-06-24 00:00:00.000000 | 2022-08-24 13:30:00.000000 | 2022-06-26 13:38:08.330000 | 2022-06-26 13:38:08.330000 |
| 6 | 1 | 荑좏룿A | ?ㅻ챸A | 3000 | CREATED | 2022-06-24 00:00:00.000000 | 2022-08-24 13:30:00.000000 | 2022-06-30 11:59:55.575000 | 2022-06-30 11:59:55.575000 |
| 8 | 1 | 荑좏룿A | ?ㅻ챸A | 3000 | CREATED | 2022-06-24 00:00:00.000000 | 2022-08-24 13:30:00.000000 | 2022-06-30 13:17:49.072000 | 2022-06-30 13:17:49.072000 |
+----+-----------+---------+-------------+-----------+---------+----------------------------+----------------------------+----------------------------+----------------------------+
7 rows in set (0.01 sec)
mysql> select count(*) from coupon;
+----------+
| count(*) |
+----------+
| 7 |
+----------+
1 row in set (0.00 sec)
mysql> select count(12321) from coupon;
+--------------+
| count(12321) |
+--------------+
| 7 |
+--------------+
1 row in set (0.00 sec)
mysql> select count(1) from coupon;
+----------+
| count(1) |
+----------+
| 7 |
+----------+
1 row in set (0.00 sec)
mysql> select count(id) from coupon;
+-----------+
| count(id) |
+-----------+
| 7 |
+-----------+
1 row in set (0.00 sec)
MyISAM 스토리지 엔진을 사용하는 테이블은 항상 테이블의 메타 정보에 전체 레코드 건수를 관리함
그래서 SELECT COUNT(*) FROM tb_table
과 같이 WHERE 조건이 없는 COUNT(*)
쿼리는 MySQL 서버가 실제 레코드 건수를 세어보지 않아도 바로 결과를 반환할 수 있기 때문에 빠르게 처리됨
WHERE 조건이 있는 COUNT(*)
쿼리는 조건에 일치하는 레코드를 읽어야 해서 일반적인 DBMS와 같이 처리
InnoDB 스토리지 엔진에 경우 WHERE 조건이 없는 COUNT(*)
쿼리라 해도 직접 데이터나 인덱스를 읽어야만 레코드 건수를 가져올 수 있다
테이블이 가진 대략적인 레코드 건수로 충분하다면 SHOW TABLE STATUS
명령으로 통계 정보를 참조하는 것도 좋은 방법
ANALYZE TABLE
명령으로 통계 정보를 갱신하면 됨COUNT(*)
쿼리에서 가장 많이 하는 실수
페이징 처리를 위해 사용할 때가 많은데, 이 때 많은 개발자들이 SELECT 쿼리를 그대로 복사해 칼럼이 명시된 부분을 삭제하고 COUNT(*) 함수로 대체해서 사용
이 때 단순히 COUNT(*) 만 실행하는 쿼리임에도 ORDER BY나 별도의 체크 조건을 가지지 않은 LEFT JOIN이 사용된 채로 실행될 때가 많다
MySQL 8.0 버전부터는 SELECT COUNT(*) 쿼리에 사용된 ORDER BY 절은 옵티마이저가 무시하도록 개선됨
일반적으로 칼럼의 값을 SELECT 하는 쿼리보다 COUNT(*) 쿼리가 훨씬 빠르게 실행될 것으로 생각
COUNT() 함수에 칼럼명이나 표현식이 인자로 사용되면 칼럼이나 표현식이 NULL이 아닌 레코드 건수만 반환
JOIN이 어떻게 인덱스를 사용하는지 쿼리 패턴별로 살펴보기
인덱스 레인지 스캔 두 과정
인덱스를 탐색(Index Seek)하는 단계
인덱스를 스캔(Index Scan)하는 단계
인덱스를 이용해서 쿼리하는 작업은 가져오는 레코드는 건수가 소량이기 때문에 인덱스 스캔작업은 부하가 작다
특정 인덱스 키를 찾는 인덱스 탐색 작업은 상대적으로 부하가 높다
조인 작업에서 드라이빙 테이블을 읽을 때 인덱스 탐색 작업 단 한번만 수행, 그 이후 스캔만 실행하면 됨
하지만 드리븐 테이블에서는 인덱스 탐색 작업과 스캔 작업을 드라이빙 테이블에서 읽은 레코드 건수만큼 반복
드라이빙 테이블과 드리븐 테이블이 1:1로 조인되더라도 드리븐 테이블을 읽는 것이 훨씬 더 큰 부하를 가짐
그래서 옵티마이저는 항상 드리븐 테이블을 최적으로 읽을 수 있게 실행 계획을 수립한다
SELECT *
FROM employees e, dept_emp de
WHERE e.emp_no=de.emp_no;
이 두 테이블의 조인쿼리에서 employees 테이블의 emp_no 칼럼과 dept_emp 테이블의 emp_no 칼럼에 각각 인덱스가 있을 때와 없을 때 조인 순서
두 칼럼 모두 각각 인덱스가 있는 경우
employees.emp_no
에만 인덱스가 있는 경우
e.emp_no=100001
과 같이 employees 테이블을 아주 효율적으로 접근 할 수 있는 조건이 있더라도 employees 테이블을 드라이빙 테이블로 선택하지 않을 가능성이 높다dept_emp.emp_no
에만 인덱스가 있는 경우
두 칼럼 모두 인덱스가 없는 경우
WHERE 절에서와 마찬가지로 테이블의 조인 조건에서도 비교 대상 칼럼과 표현식의 데이터 타입을 동일하게 사용해야한다
# tb_test1.user_type은 INT 타입, tb_test2.user_type은 CHAR(1)
SELECT *
FROM tb_test1 tb1, tb_test2 tb2
WHERE tb1.user_type=tb2.user_type;
tb_test2 테이블의 user_type 칼럼은 PK라서 이 쿼리에서 최소한 드리븐 테이블은 PK키를 이용한 인덱스 레인지 스캔을 사용해 조인이 처리될 것으로 예상됨
하지만 실제 실행 계획에서 두 테이블 모두 풀 테이블 스캔으로 접근했다
실행 계획 두 번째 줄의 Extra 칼럼을 보면 조인 버퍼를 이용한 해시 조인이 실행된 것을 알 수 있다
11.4.3.2 문자열이나 숫자 비교에서 살펴본 것과 같은 것
비교 조건의 양쪽 항이 테이터 타입이 달라서 tb_test2 테이블의 user_type 칼럼을 CHAR(1)에서 INT로 변환해서 비교를 수행하게된다
옵티마이저는 드리븐 테이블이 인덱스 레인지 스캔이 아닌 풀 테이블 스캔이 필요한 것을 알고 조금 더 빠르게 처리하고자 조인 버퍼를 활용한 해시 조인을 택하게됨
인덱스 사용에 영향을 미치는 데이터 타입 불일치 문제가 발생하지 않는 데이터 관계
문제가 될 가능성이 높은 대표적인 패턴들
87p 쿼리를 보면
각 테이블의 user_type 칼럼의 타입을 CHAR(1)로 똑같이 했지만 COLLATE가 다름
그래서 실행 계획을 보면 드리븐 테이블을 풀 테이블 스캔하는 실행 계획으로 조인이 실행됐기 때문에
옵티마이저가 조인 버퍼를 사용함
표준화 규칙을 가지고 데이터 모델링된 경우에는 이러한 케이스가 잘 발생하지 않지만 조금씩 데이터 모델을 변경하다 보면 이런 현상이 자주 발생
Character Set
Collation
다음 스터디
2023-01-17 화 pm 10:00
학습 범위
정리 범위