도서 소개
MySQL 서버를 활용하는 프로젝트에 꼭 필요한 경험과 지식을 담았습니다!
《Real MySQL 8.0》은 《Real MySQL》을 정제해서 꼭 필요한 내용으로 압축하고, MySQL 8.0의 GTID와 InnoDB 클러스터 기능들과 소프트웨어 업계 트렌드를 반영한 GIS 및 전문 검색 등의 확장 기능들을 추가로 수록했다.
또한 《Real MySQL 8.0》은 단순 SQL 문법이나 쿼리 작성보다는 MySQL 서버를 활용하는 프로젝트에서 꼭 필요한 경험과 지식을 전달하는 데 집중했다.
이 책을 처음부터 끝까지 정독할 수 있다면 더없이 좋겠지만 필요하거나 관심 있는 내용 위주로 살펴봐도 지금까지 경험했던 수많은 문제들을 해결할 수 있을 것이다.
★ 이 책에서 다루는 내용 ★
- MySQL 설치와 서버 아키텍처
- 트랜잭션과 잠금
- 인덱스 종류의 구조 및 활용
- GTID 기반 복제 및 InnoDB 클러스터 아키텍처
- 쿼리의 실행 계획 분석 및 최적화
- 스토어드 프로그램 개발
- 데이터 모델링 및 최적의 데이터 타입 선정
- 파티션 및 데이터 압축 활용
- GIS 및 전문 검색 기능 활용
- Performance 스키마 및 Sys 스키마 활용
도서 상세 이미지

저자 소개
백은빈
컴퓨터 공학을 전공하고 카카오에서 MySQL/MongoDB DBA로 근무하면서 DB 운영 및 트러블슈팅과 데이터 모델링, DBMS 도구 개발 및 신기술 벤치마킹을 수행했으며, 현재 당근마켓에서 MySQL/MongoDB DBA로 근무하고 있다.
이성욱
컴퓨터 과학을 전공하고 네이버와 라인을 거쳐 카카오 및 밸런스히어로에서 DBA로 근무했으며, 현재 당근마켓 DB팀 리더로 근무하고 있다.
목차
- ▣ 01장: 소개
- 1.1 MySQL 소개
- 1.2 왜 MySQL인가?
- ▣ 02장: 설치와 설정
- 2.1 MySQL 서버 설치
- 2.1.1 버전과 에디션(엔터프라이즈와 커뮤니티) 선택
- 2.1.2 MySQL 설치
- 2.2 MySQL 서버의 시작과 종료
- 2.2.1 설정 파일 및 데이터 파일 준비
- 2.2.2 시작과 종료
- 2.2.3 서버 연결 테스트
- 2.3 MySQL 서버 업그레이드
- 2.3.1 인플레이스 업그레이드 제약 사항
- 2.3.2 MySQL 8.0 업그레이드 시 고려 사항
- 2.3.3 MySQL 8.0 업그레이드
- 2.4 서버 설정
- 2.4.1 설정 파일의 구성
- 2.4.2 MySQL 시스템 변수의 특징
- 2.4.3 글로벌 변수와 세션 변수
- 2.4.4 정적 변수와 동적 변수
- 2.4.5 SET PERSIST
- 2.4.6 my.cnf 파일
- 2.1 MySQL 서버 설치
- ▣ 03장: 사용자 및 권한
- 3.1 사용자 식별
- 3.2 사용자 계정 관리
- 3.2.1 시스템 계정과 일반 계정
- 3.2.2 계정 생성
- 3.3 비밀번호 관리
- 3.3.1 고수준 비밀번호
- 3.3.2 이중 비밀번호
- 3.4 권한(Privilege)
- 3.5 역할(Role)
- ▣ 04장: 아키텍처
- 4.1 MySQL 엔진 아키텍처
- 4.1.1 MySQL의 전체 구조
- 4.1.2 MySQL 스레딩 구조
- 4.1.3 메모리 할당 및 사용 구조
- 4.1.4 플러그인 스토리지 엔진 모델
- 4.1.5 컴포넌트
- 4.1.6 쿼리 실행 구조
- 4.1.7 복제
- 4.1.8 쿼리 캐시
- 4.1.9 스레드 풀
- 4.1.10 트랜잭션 지원 메타데이터
- 4.2 InnoDB 스토리지 엔진 아키텍처
- 4.2.1 프라이머리 키에 의한 클러스터링
- 4.2.2 외래 키 지원
- 4.2.3 MVCC(Multi Version Concurrency Control)
- 4.2.4 잠금 없는 일관된 읽기(Non-Locking Consistent Read)
- 4.2.5 자동 데드락 감지
- 4.2.6 자동화된 장애 복구
- 4.2.7 InnoDB 버퍼 풀
- 4.2.8 Double Write Buffer
- 4.2.9 언두 로그
- 4.2.10 체인지 버퍼
- 4.2.11 리두 로그 및 로그 버퍼
- 4.2.12 어댑티브 해시 인덱스
- 4.2.13 InnoDB와 MyISAM, MEMORY 스토리지 엔진 비교
- 4.3 MyISAM 스토리지 엔진 아키텍처
- 4.3.1 키 캐시
- 4.3.2 운영체제의 캐시 및 버퍼
- 4.3.3 데이터 파일과 프라이머리 키(인덱스) 구조
- 4.4 MySQL 로그 파일
- 4.4.1 에러 로그 파일
- 4.4.2 제너럴 쿼리 로그 파일(제너럴 로그 파일, General log)
- 4.4.3 슬로우 쿼리 로그
- 4.1 MySQL 엔진 아키텍처
- ▣ 05장: 트랜잭션과 잠금
- 5.1 트랜잭션
- 5.1.1 MySQL에서의 트랜잭션
- 5.1.2 주의사항
- 5.2 MySQL 엔진의 잠금
- 5.2.1 글로벌 락
- 5.2.2 테이블 락
- 5.2.3 네임드 락
- 5.2.4 메타데이터 락
- 5.3 InnoDB 스토리지 엔진 잠금
- 5.3.1 InnoDB 스토리지 엔진의 잠금
- 5.3.2 인덱스와 잠금
- 5.3.3 레코드 수준의 잠금 확인 및 해제
- 5.4 MySQL의 격리 수준
- 5.4.1 READ UNCOMMITTED
- 5.4.2 READ COMMITTED
- 5.4.3 REPEATABLE READ
- 5.4.4 SERIALIZABLE
- 5.1 트랜잭션
- ▣ 06장: 데이터 압축
- 6.1 페이지 압축
- 6.2 테이블 압축
- 6.2.1 압축 테이블 생성
- 6.2.2 KEY_BLOCK_SIZE 결정
- 6.2.3 압축된 페이지의 버퍼 풀 적재 및 사용
- 6.2.4 테이블 압축 관련 설정
- ▣ 07장: 데이터 암호화
- 7.1 MySQL 서버의 데이터 암호화
- 7.1.1 2단계 키 관리
- 7.1.2 암호화와 성능
- 7.1.3 암호화와 복제
- 7.2 keyring_file 플러그인 설치
- 7.3 테이블 암호화
- 7.3.1 테이블 생성
- 7.3.2 응용 프로그램 암호화와의 비교
- 7.3.3 테이블스페이스 이동
- 7.4 언두 로그 및 리두 로그 암호화
- 7.5 바이너리 로그 암호화
- 7.5.1 바이너리 로그 암호화 키 관리
- 7.5.2 바이너리 로그 암호화 키 변경
- 7.5.3 mysqlbinlog 도구 활용
- 7.1 MySQL 서버의 데이터 암호화
- ▣ 08장: 인덱스
- 8.1 디스크 읽기 방식
- 8.1.1 하드 디스크 드라이브(HDD)와 솔리드 스테이트 드라이브(SSD)
- 8.1.2 랜덤 I/O와 순차 I/O
- 8.2 인덱스란?
- 8.3 B-Tree 인덱스
- 8.3.1 구조 및 특성
- 8.3.2 B-Tree 인덱스 키 추가 및 삭제
- 8.3.3 B-Tree 인덱스 사용에 영향을 미치는 요소
- 8.3.4 B-Tree 인덱스를 통한 데이터 읽기
- 8.3.5 다중 칼럼(Multi-column) 인덱스
- 8.3.6 B-Tree 인덱스의 정렬 및 스캔 방향
- 8.3.7 B-Tree 인덱스의 가용성과 효율성
- 8.4 R-Tree 인덱스
- 8.4.1 구조 및 특성
- 8.4.2 R-Tree 인덱스의 용도
- 8.5 전문 검색 인덱스
- 8.5.1 인덱스 알고리즘
- 8.5.2 전문 검색 인덱스의 가용성
- 8.6 함수 기반 인덱스
- 8.6.1 가상 칼럼을 이용한 인덱스
- 8.6.2 함수를 이용한 인덱스
- 8.7 멀티 밸류 인덱스
- 8.8 클러스터링 인덱스
- 8.8.1 클러스터링 인덱스
- 8.8.2 세컨더리 인덱스에 미치는 영향
- 8.8.3 클러스터링 인덱스의 장점과 단점
- 8.8.4 클러스터링 테이블 사용 시 주의사항
- 8.9 유니크 인덱스
- 8.9.1 유니크 인덱스와 일반 세컨더리 인덱스의 비교
- 8.9.2 유니크 인덱스 사용 시 주의사항
- 8.10 외래키
- 8.10.1 자식 테이블의 변경이 대기하는 경우
- 8.10.2 부모 테이블의 변경 작업이 대기하는 경우
- 8.1 디스크 읽기 방식
- ▣ 09장: 옵티마이저와 힌트
- 9.1 개요
- 9.1.1 쿼리 실행 절차
- 9.1.2 옵티마이저의 종류
- 9.2 기본 데이터 처리
- 9.2.1 풀 테이블 스캔과 풀 인덱스 스캔
- 9.2.2 병렬 처리
- 9.2.3 ORDER BY 처리(Using filesort)
- 9.2.4 GROUP BY 처리
- 9.2.5 DISTINCT 처리
- 9.2.6 내부 임시 테이블 활용
- 9.3 고급 최적화
- 9.3.1 옵티마이저 스위치 옵션
- 9.3.2 조인 최적화 알고리즘
- 9.4 쿼리 힌트
- 9.4.1 인덱스 힌트
- 9.4.2 옵티마이저 힌트
- 9.1 개요
- ▣ 10장: 실행 계획
- 10.1 통계 정보
- 10.1.1 테이블 및 인덱스 통계 정보
- 10.1.2 히스토그램
- 10.1.3 코스트 모델(Cost Model)
- 10.2 실행 계획 확인
- 10.2.1 실행 계획 출력 포맷
- 10.2.2 쿼리의 실행 시간 확인
- 10.3 실행 계획 분석
- 10.3.1 id 칼럼
- 10.3.2 select_type 칼럼
- 10.3.3 table 칼럼
- 10.3.4 partitions 칼럼
- 10.3.5 type 칼럼
- 10.3.6 possible_keys 칼럼
- 10.3.7 key 칼럼
- 10.3.8 key_len 칼럼
- 10.3.9 ref 칼럼
- 10.3.10 rows 칼럼
- 10.3.11 filtered 칼럼
- 10.3.12 Extra 칼럼
- 10.1 통계 정보
예제 코드
- GitHub 저장소: https://github.com/wikibook/realmysql80
- ZIP 형식으로 다운로드: https://github.com/wikibook/realmysql80/archive/refs/heads/main.zip
정오표
-
8쪽, 12번째 줄
모니터링 도구 또는
PeconaServer에서==>
모니터링 도구 또는
PerconaServer에서 -
51쪽, 설정 파일 예시에서 맨 마지막 줄
innodb_io_capacity=400==>
innodb_io_capacity_max=400 -
69쪽, 본문 11번째 줄(3번째 단락 3번째 줄)
dept_name
칼럼을 업데이트할 수 없게권한을 부여하려면==>
dept_name
칼럼만 업데이트할 수 있게권한을 부여하려면 -
100쪽, 본문 첫 번째 줄
멈추면
대략레코드 적재나==>
멈추면
대량레코드 적재나 -
102쪽, 본문 밑에서 8번째 줄
격리 수준이 READ_UNCOMMITTED인 경우에는
InnoDB 버퍼 풀이나 데이터 파일로부터 변경되지 않은 데이터를읽어서 반환한다.==>
격리 수준이 READ_UNCOMMITTED인 경우에는
InnoDB 버퍼 풀이 현재 가지고 있는 변경된 데이터를읽어서 반환한다. -
103쪽, 그림 4.12를 다음 그림으로 교체

-
103쪽, 그림 4.12의 제목
그림 4.12 잠겨진 레코드 읽기
==>
그림 4.12 변경 중인(COMMIT 전) 레코드 읽기
-
106쪽, 본문 밑에서 7번째 줄
일반적으로 MySQL 서버는 다시 시작하면서
언두 영역의데이터를 먼저 데이터 파일에 적용하고 그다음리두로그의 내용을 다시 덮어써서 장애시점의 데이터를만들어낸다.==>
일반적으로 MySQL 서버는 다시 시작하면서
리두 로그의데이터를 먼저 데이터 파일에 적용하고, 그다음언두로그의 내용을 다시 덮어써서 장애직전 시점의 데이터 상태를만들어낸다. -
126쪽, 본문 밑에서 4번째 줄
대략
2097152(=16*1024 * 128 * 2 / 2)개 정도의==>
대략
131072(=16 * 1024 / 16 * 128 * 2 / 2)개 정도의 -
127쪽, 실습 내용의 두 번째 명령어
mysql> CREATE UNDO TABLESPACE extra_undo_003 ADD DATAFILE '/data/undo_dir/undo_003';==>
mysql> CREATE UNDO TABLESPACE extra_undo_003 ADD DATAFILE '/data/undo_dir/undo_003.ibu'; -
132쪽, 본문 5번째 줄
그래서 트랜잭션이 일단 커밋되면 해당 트랜잭션에서 변경한 데이터는
사라진다.==>
그래서 트랜잭션이 일단 커밋되면 해당 트랜잭션에서 변경한 데이터는
사라지지 않는다. -
166쪽, 페이지 중간 실습 내용에서 8번째 줄
mysql> SELECT MAX(id) as @MAX_ID FROM access_log;==>
mysql> SELECT MAX(id) as @MAX_ID FROM access_log_new; -
173쪽, 페이지 하단 명령 실행 결과를 다음 내용으로 교체
mysql> SHOW PROCESSLIST; +----+--------+----------+-----------------------------------------------------------+ | Id | Time | State | Info | +----+--------+----------+-----------------------------------------------------------+ | 17 | 607 | | NULL | | 18 | 22 | updating | UPDATE employees SET hire_date=NOW() WHERE emp_no=100001; | | 19 | 21 | updating | UPDATE employees SET hire_date=NOW(), birth_date=NOW()... | +----+--------+----------+-----------------------------------------------------------+ -
182쪽, 그림 5.7을 다음 그림으로 교체(사용자 B의 쿼리를 수정)

-
201쪽, 하단 '주의' 박스의 5번째 줄
키링 파일을 삭제해도 MySQL 서버가 작동하는 데는 아무런 문제가 없다.
==>
키링 파일을 삭제해도
(MySQL 서버가 재시작되지 않는 한)MySQL 서버가 작동하는 데는 아무런 문제가 없다. -
222쪽, 그림 8.6을 다음 그림으로 교체

-
224쪽, 본문 2~6번째 줄
대략적으로 계산하는 방법은 테이블에 레코드를 추가하는 작업 비용을 1이라고 가정하면 해당 테이블의 인덱스에 키를 추가하는 작업 비용을 1.5 정도로 예측하는 것이다. 일반적으로 테이블에 인덱스가 3개(테이블의 모든 인덱스가 B-Tree라는 가정하에)가 있다면 이때 테이블에 인덱스가 하나도 없는 경우는 작업 비용이 1이고, 3개인 경우에는 5.5 정도의 비용(1.5 * 3 + 1) 정도로 예측한다.
==>
대략적으로 계산하는 방법은 AutoIncrement 프라이머리 키 또는 단조 증가 패턴의 프라이머리 키를 사용하는 InnoDB 테이블 그리고 MyISAM 테이블의 경우, 테이블에 레코드를 추가하는 작업 비용을 1이라고 가정하면 해당 테이블의 인덱스에 키를 추가하는 작업 비용도 비슷하게 1.5 정도로 예측할 수 있다. 그리고 랜덤한 순서를 가지는 프라이머리 키의 InnoDB 테이블에서는 테이블에 레코드를 추가하는 비용과 인덱스를 추가하는 비용을 비슷하게 1정도로 예측해볼 수 있다.
일반적으로 테이블에 인덱스가 3개(테이블의 모든 인덱스가 B-Tree라는 가정하에)가 있다면 이때 테이블에 인덱스가 하나도 없는 경우 는 작업 비용이 1이고, 3개인 경우에는 4 ~ 5.5 정도의 비용(13+1 ~ 1.53+1) 정도로 예측한다.
-
242쪽, 본문 밑에서 4번째 줄
인덱스는 항상 오름차순으로만 정렬돼 있지만 인덱스를 최솟값부터 읽으면 오름차순으로 값을 가져올 수 있고, 최댓값부터 거꾸로 읽으면 내림차순으로 값을 가져올 수 있다는 것을 MySQL 옵티마이저는 이미 알고 있다.
==>
위 쿼리를 위한 ix_firstname(first_name) 인덱스는 정순 인덱스(Ascending Index)로 오름차순으로만 정렬돼 있지만, 인덱스를 최솟값부터 읽으면 오름차순으로 값을 가져올 수 있고, 최댓값부터 거꾸로 읽으면 내림차순으로 값을 가져올 수 있다는 것을 MySQL 옵티마이저는 이미 알고 있다.
-
243쪽, 밑에서 5번째 줄
물론 다음과 같이 2개 이상의 칼럼으로 구성된 복합 인덱스에서 각각의 칼럼이 내림차순과 오름차순이 혼합된 경우에는 MySQL 8.0의 내림차순 인덱스로만 해결될 수 있다.
mysql> CREATE INDEX ix_teamname_userscore ON employees (team_name ASC, user_score DESC);==>
주의
물론 2개 이상의 칼럼이 내림차순 그리고 오름차순을 동시에 사용하는 쿼리를 위해서는, 각 칼럼이 오름차순 그리고 내림차순으로 정의된 복합 칼럼 인덱스가 필요하다. 예를 들어 다음과 같이 team_name은 정순으로 정렬 후 user_score 칼럼은 역순으로 정렬하는 쿼리를 가정해보자.
mysql SELECT * FROM employees ORDER BY team_name ASC, user_score DESC LIMIT 10;이 쿼리의 정렬 작업이 인덱스를 사용하도록 튜닝하기 위해서는 (team_name ASC, user_score ASC) 인덱스로는 도움이 되지 않는다. 이런 경우에는 MySQL 8.0의 내림차순 인덱스로만 튜닝이 될 수 있다.
mysql> CREATE INDEX ix_teamname_userscore ON employees (team_name ASC, user_score DESC); -
262쪽, 표의 5행 4열
et
==>
es
-
274쪽, 본문 첫 번째 줄
클러스터링 키 값이 변경될 때마다 데이터 레코드의 주소가 변경되고 그때마다 해당 테이블의 모든 인덱스에 저장된 주솟값을 변경해야 할 것이다.
==>
데이터 페이지 스플릿이나 병합 또는 컴팩션등의 작업으로 인해서 레코드의 주소가 변경될 때마다 해당 테이블의 모든 인덱스에 저장된 주솟값을 변경해야 할 것이다.
-
298쪽, 본문 3번째 줄
WHERE 절의 검색 조건(“emp_no BETWEEN
100001AND 100010”)은==>
WHERE 절의 검색 조건(“emp_no BETWEEN
100002AND 100010”)은 -
298쪽, 본문 밑에서 4번째 줄
인덱스를 이용해 “emp_no BETWEEN
100001AND 100010” 조건을==>
인덱스를 이용해 “emp_no BETWEEN
100002AND 100010” 조건을 -
317쪽, 9.2.6.4절 본문 첫 번째 줄
사용했다는 사실을 수 있다.
==>
사용했다는 사실을
알수 있다. -
328쪽, 본문 4번째 줄
dept_emp칼럼(16바이트)까지 사용했다는 것을==>
dept_no칼럼(16바이트)까지 사용했다는 것을