Ver3.5 ๐๏ธ MySQL ์ธ๋ฑ์ค ์ค๊ณ์ ์ฟผ๋ฆฌ ์ฑ๋ฅ ๋ถ์ ์์ ์ ๋ณต ๊ฐ์ด๋

๐๏ธ MySQL ์ธ๋ฑ์ค ์ค๊ณ์ ์ฟผ๋ฆฌ ์ฑ๋ฅ ๋ถ์ ์์ ์ ๋ณต ๊ฐ์ด๋
๋๋ฆฐ ์ฟผ๋ฆฌ์ ์ง์ณ์๋ ๋น์ ์ ์ํ, ์ฐ ์ค์ ํ๋ ์ด์ผ๊ธฐ ๐
์ผ, ์์งํ ๋งํด๋ด. ๊ฐ๋ฐํ๋ค๊ฐ ์ฟผ๋ฆฌ ํ๋ ๋ ๋ ธ๋๋ฐ ๋ก๋ฉ์ด 5์ด, 10์ด์ฉ ๊ฑธ๋ ค์ ๋ฉํ ๋๊ฐ ์ ์์ง ์์? ๐
๊ทธ ์๊ฐ "์ ์ธ๋ฑ์ค ์ข ๊ฑธ์ด์ผ ํ๋..." ํ๊ณ ๋ง์ฐํ๊ฒ ์๊ฐ๋ง ํ๋ค๊ฐ ๊ฒฐ๊ตญ ๊ทธ๋ฅ ๋์ด๊ฐ ๊ฒฝํ, ๋ค๋ค ํ ๋ฒ์ฏค์ ์์ ๊ฑฐ์ผ.
์ค๋์ ๊ทธ ๋ง์ฐํจ์ ์์ ํ ๋ ๋ ค๋ฒ๋ฆด ๊ฑฐ์ผ. MySQL ์ธ๋ฑ์ค์ ์๋ฆฌ๋ถํฐ ์ค๊ณ ์ ๋ต, ์ฟผ๋ฆฌ ์ฑ๋ฅ ๋ถ์ ๋ฐฉ๋ฒ๊น์ง โ ์น๊ตฌํํ
์ค๋ช
ํ๋ฏ ์ญ ํ์ด๋ณผ๊ฒ. ์ปคํผ ํ ์ ๋ค๊ณ ํธํ๊ฒ ์ฝ์ด๋ด! โ
๐ณ ์ธ๋ฑ์ค๊ฐ ๋ญ์ง ์ ๋๋ก ์๊ณ ๊ฐ์
์ธ๋ฑ์ค๋ฅผ ํ ๋ง๋๋ก ํํํ๋ฉด? "์ฑ
์ ๋ชฉ์ฐจ"์ผ.
๋๊บผ์ด ์ฑ
์์ ํน์ ๋ด์ฉ์ ์ฐพ์ ๋, ์ฒ์๋ถํฐ ๋๊น์ง ๋ค ์ฝ๋ ์ฌ๋์ ์์์. ๋ชฉ์ฐจ ๋ณด๊ณ ํ์ด์ง ๋ฒํธ ํ์ธํ๊ณ ๋ฐ๋ก ๊ทธ ํ์ด์ง๋ก ๊ฐ์ง.
MySQL ์ธ๋ฑ์ค๋ ๋๊ฐ์. ๋ฐ์ดํฐ๋ฅผ ๋น ๋ฅด๊ฒ ์ฐพ๊ธฐ ์ํ ๋ณ๋์ ์๋ฃ๊ตฌ์กฐ์ผ.
๐ MySQL์ด ๊ธฐ๋ณธ์ผ๋ก ์ฐ๋ B-Tree ์ธ๋ฑ์ค
MySQL InnoDB ์์ง์ ๊ธฐ๋ณธ ์ธ๋ฑ์ค ๊ตฌ์กฐ๋ B+ Tree (B-Plus Tree)์ผ.
B-Tree ๊ณ์ด์ด๋ผ "B-Tree ์ธ๋ฑ์ค"๋ผ๊ณ ๋ง์ด ๋ถ๋ฅด๋๋ฐ, ์ ํํ๋ B+ Tree์ผ. ์ฐจ์ด์ ์ด ๋ญ๋๊ณ ?
B-Tree: ๋ชจ๋ ๋ ธ๋(๋ฃจํธ, ์ค๊ฐ, ๋ฆฌํ)์ ์ค์ ๋ฐ์ดํฐ ํฌ์ธํฐ๊ฐ ์์ด.
B+ Tree: ๋ฆฌํ ๋ ธ๋์๋ง ์ค์ ๋ฐ์ดํฐ ํฌ์ธํฐ๊ฐ ์๊ณ , ๋ฆฌํ ๋ ธ๋๋ค์ด ์ฐ๊ฒฐ ๋ฆฌ์คํธ๋ก ์ด์ด์ ธ ์์ด.
โ ๋๋ถ์ ๋ฒ์ ๊ฒ์(BETWEEN, >, < ๋ฑ)์ด ์์ฒญ ๋น ๋ฆ! ๋ฆฌํ ๋ ธ๋๋ง ์์๋๋ก ํ์ผ๋ฉด ๋๊ฑฐ๋ . ๐ฏ
B+ Tree์ ํต์ฌ ํน์ฑ์ ์ ๋ฆฌํ๋ฉด:
| ํน์ฑ | ๋ด์ฉ |
|---|---|
| ๊ท ํ ํธ๋ฆฌ | ๋ชจ๋ ๋ฆฌํ ๋ ธ๋๊น์ง์ ๊น์ด๊ฐ ๋์ผ โ ํ์ ์๊ฐ์ด ํญ์ O(log N) |
| ์ ๋ ฌ๋ ์ํ ์ ์ง | ์ฝ์ /์ญ์ ์ ์๋์ผ๋ก ์ฌ์ ๋ ฌ โ ํญ์ ์ ๋ ฌ๋ ์ํ |
| ๋ฆฌํ ๋ ธ๋ ์ฐ๊ฒฐ | ๋ฒ์ ๊ฒ์ ์ ์ฐ๊ฒฐ ๋ฆฌ์คํธ ์ํ๋ก ๋น ๋ฅธ ์ฒ๋ฆฌ |
| ํ์ด์ง ๋จ์ ์ ์ฅ | InnoDB ๊ธฐ๋ณธ ํ์ด์ง ํฌ๊ธฐ 16KB โ ๋์คํฌ I/O ์ต์ํ |
๐๏ธ ํด๋ฌ์คํฐ๋ ์ธ๋ฑ์ค vs ์ธ์ปจ๋๋ฆฌ ์ธ๋ฑ์ค
InnoDB์์ ์ธ๋ฑ์ค๋ฅผ ์ดํดํ๋ ค๋ฉด ์ด ๋ ๊ฐ์ง ๊ฐ๋ ์ด ํต์ฌ์ด์ผ!
ํ ์ด๋ธ๋น ๋ฑ ํ๋๋ง ์กด์ฌํด.
PRIMARY KEY๊ฐ ๋ฐ๋ก ํด๋ฌ์คํฐ๋ ์ธ๋ฑ์ค์ผ.
์ค์ ๋ฐ์ดํฐ ํ์ด ์ด ์ธ๋ฑ์ค ์์๋๋ก ๋ฌผ๋ฆฌ์ ์ผ๋ก ์ ์ฅ๋ผ.
โ PK๋ก ์กฐํํ๋ฉด ์ด๊ณ ์์ธ ์ด์ ๊ฐ ๋ฐ๋ก ์ด๊ฒ!
PK ์ธ์ ์ถ๊ฐ๋ก ๋ง๋๋ ๋ชจ๋ ์ธ๋ฑ์ค.
๋ฆฌํ ๋ ธ๋์ ์ค์ ๋ฐ์ดํฐ ๋์ PK ๊ฐ์ ์ ์ฅํด.
์กฐํ ์ ์ธ์ปจ๋๋ฆฌ ์ธ๋ฑ์ค โ PK โ ์ค์ ๋ฐ์ดํฐ ์์ผ๋ก 2๋จ๊ณ ํ์.
โ ์ด๊ฑธ "๋๋ธ ๋ฃฉ์ "์ด๋ผ๊ณ ํด.
์ธ์ปจ๋๋ฆฌ ์ธ๋ฑ์ค๋ ๋ฆฌํ ๋ ธ๋์ PK๋ฅผ ์ ์ฅํ๊ธฐ ๋๋ฌธ์, PK๊ฐ ํฌ๋ฉด ํด์๋ก ์ธ์ปจ๋๋ฆฌ ์ธ๋ฑ์ค๋ ์ปค์ ธ.
UUID(36์๋ฆฌ ๋ฌธ์์ด)๋ฅผ PK๋ก ์ฐ๋ฉด ๋ชจ๋ ์ธ์ปจ๋๋ฆฌ ์ธ๋ฑ์ค๊ฐ ๋ฑ๋ฑํด์ง๋ ์ด์ ์ผ. INT๋ BIGINT PK๋ฅผ ๊ถ์ฅํ๋ ์ด์ ๊ฐ ์ฌ๊ธฐ ์์ด!
๐ฏ ์ธ๋ฑ์ค ์ข ๋ฅ ์์ ์ ๋ฆฌ
MySQL์์ ์ธ ์ ์๋ ์ธ๋ฑ์ค ์ข ๋ฅ๋ฅผ ์น ์ ๋ฆฌํด๋ณผ๊ฒ. ์ํฉ์ ๋ง๊ฒ ๊ณจ๋ผ ์จ์ผ ์ง์ง ํจ๊ณผ๋ฅผ ๋ณผ ์ ์์ด!
1๏ธโฃ ๋จ์ผ ์ปฌ๋ผ ์ธ๋ฑ์ค (Single Column Index)
-- ๊ธฐ๋ณธ ์ธ๋ฑ์ค ์์ฑ
CREATE INDEX idx_email ON users(email);
-- ๋๋ ํ
์ด๋ธ ์์ฑ ์
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL,
name VARCHAR(100),
INDEX idx_email (email)
);
๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ํํ์ผ. ํ๋์ ์ปฌ๋ผ์๋ง ์ธ๋ฑ์ค๋ฅผ ๊ฑธ์ด. ๋จ์ ์กฐํ์๋ ์ถฉ๋ถํ์ง๋ง, ๋ณตํฉ ์กฐ๊ฑด ์ฟผ๋ฆฌ์์๋ ํ๊ณ๊ฐ ์์ด.
2๏ธโฃ ๋ณตํฉ ์ธ๋ฑ์ค (Composite Index) โ ํต์ฌ ์ค์ ํต์ฌ!
-- ๋ณตํฉ ์ธ๋ฑ์ค ์์ฑ CREATE INDEX idx_name_age ON users(last_name, first_name, age); -- ์ด ์ธ๋ฑ์ค๊ฐ ํจ๊ณผ ์๋ ์ฟผ๋ฆฌ๋ค: SELECT * FROM users WHERE last_name = '๊น'; -- โ ์ฌ์ฉ ๊ฐ๋ฅ SELECT * FROM users WHERE last_name = '๊น' AND first_name = '์ฒ ์'; -- โ ์ฌ์ฉ ๊ฐ๋ฅ SELECT * FROM users WHERE last_name = '๊น' AND age = 25; -- โ ๋ถ๋ถ ์ฌ์ฉ -- ์ด ์ธ๋ฑ์ค๊ฐ ํจ๊ณผ ์๋ ์ฟผ๋ฆฌ๋ค: SELECT * FROM users WHERE first_name = '์ฒ ์'; -- โ ์ฌ์ฉ ๋ถ๊ฐ SELECT * FROM users WHERE age = 25; -- โ ์ฌ์ฉ ๋ถ๊ฐ
๋ณตํฉ ์ธ๋ฑ์ค๋ ์ผ์ชฝ๋ถํฐ ์์๋๋ก ์ฌ์ฉํด์ผ ํด!
(A, B, C) ์ธ๋ฑ์ค๊ฐ ์์ผ๋ฉด โ A, A+B, A+B+C ์กฐํฉ๋ง ์ธ๋ฑ์ค ํ์ฉ ๊ฐ๋ฅ.
B๋ง, C๋ง, B+C ์กฐํฉ์ ์ธ๋ฑ์ค๋ฅผ ๋ชป ์จ. ์ด๊ฒ ๋ณตํฉ ์ธ๋ฑ์ค ์ค๊ณ์ ํต์ฌ์ด์ผ!
3๏ธโฃ ์ ๋ํฌ ์ธ๋ฑ์ค (Unique Index)
-- ์ ๋ํฌ ์ธ๋ฑ์ค (์ค๋ณต๊ฐ ๋ถํ) CREATE UNIQUE INDEX idx_unique_email ON users(email); -- UNIQUE ์ ์ฝ์กฐ๊ฑด๋ ๋ด๋ถ์ ์ผ๋ก ์ ๋ํฌ ์ธ๋ฑ์ค ์์ฑ ALTER TABLE users ADD CONSTRAINT uk_email UNIQUE (email);
์ค๋ณต๊ฐ์ ํ์ฉํ์ง ์๋ ์ธ๋ฑ์ค์ผ. ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ๋ณด์ฅ + ์ฑ๋ฅ ํฅ์ ๋ ๋ง๋ฆฌ ํ ๋ผ๋ฅผ ์ก์ ์ ์์ด.
MySQL์ ์ ๋ํฌ ์ธ๋ฑ์ค์์ ๊ฐ์ ์ฐพ์ผ๋ฉด ๋ ์ด์ ํ์์ ์ ํด๋ ๋๋๊น ์ผ๋ฐ ์ธ๋ฑ์ค๋ณด๋ค ์ด์ง ๋ ๋น ๋ฅผ ์ ์์ด.
4๏ธโฃ ์ปค๋ฒ๋ง ์ธ๋ฑ์ค (Covering Index) โ ์ฑ๋ฅ ๋ง๋ฒ!
-- ์์: ์ฃผ๋ฌธ ํ ์ด๋ธ์์ user_id๋ณ ์ต๊ทผ ์ฃผ๋ฌธ ์กฐํ CREATE INDEX idx_user_status_date ON orders(user_id, status, created_at); -- ์ด ์ฟผ๋ฆฌ๋ ์ธ๋ฑ์ค๋ง์ผ๋ก ์์ ํ ์ฒ๋ฆฌ ๊ฐ๋ฅ! (ํ ์ด๋ธ ์ ๊ทผ ๋ถํ์) SELECT user_id, status, created_at FROM orders WHERE user_id = 123 AND status = 'completed' ORDER BY created_at DESC;
์ฟผ๋ฆฌ์์ ํ์ํ ๋ชจ๋ ์ปฌ๋ผ์ด ์ธ๋ฑ์ค ์์ ํฌํจ๋์ด ์์ด์, ์ค์ ํ ์ด๋ธ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ง ์์๋ ๋๋ ์ํ์ผ.
EXPLAIN ๊ฒฐ๊ณผ์์
Extra: Using index๊ฐ ๋จ๋ฉด ์ปค๋ฒ๋ง ์ธ๋ฑ์ค๊ฐ ์ ์ฉ๋ ๊ฑฐ์ผ. ์ด๊ฒ ๋จ๋ฉด ์ฑ๋ฅ ์ต๊ณ ! ๐
5๏ธโฃ ์ ๋ฌธ ๊ฒ์ ์ธ๋ฑ์ค (Full-Text Index)
-- ์ ๋ฌธ ๊ฒ์ ์ธ๋ฑ์ค ์์ฑ
CREATE FULLTEXT INDEX idx_ft_content ON articles(title, content);
-- MATCH ... AGAINST ๋ฌธ๋ฒ์ผ๋ก ๊ฒ์
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('MySQL ์ธ๋ฑ์ค' IN BOOLEAN MODE);
-- IN BOOLEAN MODE์์ ์ธ ์ ์๋ ์ฐ์ฐ์
-- +๋จ์ด: ๋ฐ๋์ ํฌํจ
-- -๋จ์ด: ๋ฐ๋์ ์ ์ธ
-- *: ์์ผ๋์นด๋
-- "๋ฌธ๊ตฌ": ์ ํํ ๋ฌธ๊ตฌ ๊ฒ์
LIKE '%๊ฒ์์ด%' ์ฐ๋ฉด ์ธ๋ฑ์ค๋ฅผ ๋ชป ์ฐ๋ ๊ฑฐ ์์ง? ํ
์คํธ ๊ฒ์์ด ํ์ํ๋ฉด Full-Text Index๋ฅผ ์จ์ผ ํด.
๋จ, ํ๊ตญ์ด๋ ํํ์ ๋ถ์๊ธฐ๊ฐ ํ์ํด์ ๊ธฐ๋ณธ ์ค์ ์ผ๋ก๋ ํ๊ณ๊ฐ ์์ด. ๋์ฉ๋ ํ
์คํธ ๊ฒ์์ด๋ผ๋ฉด Elasticsearch๋ฅผ ๊ณ ๋ คํด๋ด.
6๏ธโฃ ํจ์ ๊ธฐ๋ฐ ์ธ๋ฑ์ค (Functional Index) โ MySQL 8.0+
-- MySQL 8.0๋ถํฐ ์ง์ํ๋ ํจ์ ๊ธฐ๋ฐ ์ธ๋ฑ์ค CREATE INDEX idx_lower_email ON users((LOWER(email))); -- ์ด์ ์ด ์ฟผ๋ฆฌ๊ฐ ์ธ๋ฑ์ค๋ฅผ ํ ์ ์์ด! SELECT * FROM users WHERE LOWER(email) = 'test@example.com'; -- JSON ์ปฌ๋ผ์๋ ์ ์ฉ ๊ฐ๋ฅ CREATE INDEX idx_json_name ON users((JSON_UNQUOTE(JSON_EXTRACT(data, '$.name'))));
๐ฌ EXPLAIN์ผ๋ก ์ฟผ๋ฆฌ ์ฑ๋ฅ ๋ถ์ํ๊ธฐ
์ธ๋ฑ์ค๋ฅผ ๊ฑธ์๋ค๊ณ ๋์ด ์๋์ผ! ์ค์ ๋ก ์ธ๋ฑ์ค๊ฐ ์ ๋๋ก ์ฌ์ฉ๋๊ณ ์๋์ง ํ์ธํด์ผ ํด.
MySQL์ EXPLAIN ๋ช
๋ น์ด๊ฐ ๋ฐ๋ก ๊ทธ ๋๊ตฌ์ผ. ๊ฐ๋ฐ์์ ํ์ ๋ฌด๊ธฐ๋ผ๊ณ ํ ์ ์์ง! ๐ซ
-- ๊ธฐ๋ณธ EXPLAIN EXPLAIN SELECT * FROM orders WHERE user_id = 123; -- ๋ ์์ธํ ์ ๋ณด (MySQL 8.0+) EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 123; -- JSON ํ์์ผ๋ก ์ถ๋ ฅ EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE user_id = 123;
๐ EXPLAIN ๊ฒฐ๊ณผ ์ปฌ๋ผ ํด์
| ์ปฌ๋ผ๋ช | ์๋ฏธ | ์ฃผ๋ชฉ ํฌ์ธํธ |
|---|---|---|
| id | ์ฟผ๋ฆฌ ์คํ ์์ | ์ซ์๊ฐ ํด์๋ก ๋จผ์ ์คํ |
| select_type | SELECT ์ ํ | SIMPLE, SUBQUERY, DERIVED ๋ฑ |
| table | ์ ๊ทผํ๋ ํ ์ด๋ธ | - |
| type | โญ ์ ๊ทผ ๋ฐฉ์ (๊ฐ์ฅ ์ค์!) | ์๋ ์์ธ ์ค๋ช ์ฐธ๊ณ |
| possible_keys | ์ฌ์ฉ ๊ฐ๋ฅํ ์ธ๋ฑ์ค ๋ชฉ๋ก | - |
| key | ์ค์ ์ฌ์ฉ๋ ์ธ๋ฑ์ค | NULL์ด๋ฉด ์ธ๋ฑ์ค ๋ฏธ์ฌ์ฉ |
| key_len | ์ฌ์ฉ๋ ์ธ๋ฑ์ค ๊ธธ์ด(๋ฐ์ดํธ) | ๋ณตํฉ ์ธ๋ฑ์ค ํ์ฉ๋ ํ์ |
| rows | ์์ ๊ฒ์ฌ ํ ์ | ์์์๋ก ์ข์ |
| filtered | ์กฐ๊ฑด์ ๋ง๋ ํ ๋น์จ(%) | ๋์์๋ก ์ข์ |
| Extra | ์ถ๊ฐ ์ ๋ณด | Using index, Using filesort ๋ฑ |
๐ฆ type ์ปฌ๋ผ โ ์ฑ๋ฅ์ ์ ํธ๋ฑ
EXPLAIN์์ type ์ปฌ๋ผ์ด ๊ฐ์ฅ ์ค์ํด. ์ฑ๋ฅ ์์๋๋ก ๋์ดํ๋ฉด:
๐งช ์ค์ EXPLAIN ๋ถ์ ์์
-- ๋ถ์ํ ์ฟผ๋ฆฌ EXPLAIN SELECT o.id, o.total_amount, u.name FROM orders o JOIN users u ON o.user_id = u.id WHERE o.status = 'pending' AND o.created_at >= '2024-01-01' ORDER BY o.created_at DESC LIMIT 20; -- ๊ฒฐ๊ณผ ์์ (๋ฌธ์ ์๋ ๊ฒฝ์ฐ) +----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+ | id | select_type | table | type | possible_keys | key | key_len | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+ | 1 | SIMPLE | o | ALL | NULL | NULL | NULL | 500K | Using where; Using filesort | | 1 | SIMPLE | u | ref | PRIMARY | PRIMARY | 8 | 1 | | +----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+ -- orders ํ ์ด๋ธ์์ type=ALL, Using filesort ๋ฐ์ โ ๊ฐ์ ํ์! -- ํด๊ฒฐ์ฑ : ๋ณตํฉ ์ธ๋ฑ์ค ์ถ๊ฐ CREATE INDEX idx_status_created ON orders(status, created_at); -- ๊ฐ์ ํ EXPLAIN ๊ฒฐ๊ณผ +----+-------------+-------+-------+------------------+------------------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | rows | Extra | +----+-------------+-------+-------+------------------+------------------+---------+------+------+-------------+ | 1 | SIMPLE | o | range | idx_status_created | idx_status_created | 1030 | 200 | Using index condition | | 1 | SIMPLE | u | const | PRIMARY | PRIMARY | 8 | 1 | | +----+-------------+-------+-------+------------------+------------------+---------+------+------+-------------+
โก ์ฌ๋ก์ฐ ์ฟผ๋ฆฌ ๋ก๊ทธ โ ๋ฒ์ธ ์ฐพ๊ธฐ
EXPLAIN์ ํน์ ์ฟผ๋ฆฌ๋ฅผ ๋ถ์ํ ๋ ์ฐ๋ ๊ฑฐ๊ณ , ์ด๋ค ์ฟผ๋ฆฌ๊ฐ ๋๋ฆฐ์ง ๋ชจ๋ฅผ ๋๋ ์ฌ๋ก์ฐ ์ฟผ๋ฆฌ ๋ก๊ทธ๋ฅผ ์จ์ผ ํด.
MySQL์ด ์๋์ผ๋ก ๋๋ฆฐ ์ฟผ๋ฆฌ๋ฅผ ๊ธฐ๋กํด์ฃผ๋ ๊ธฐ๋ฅ์ด์ผ. ์ค๋ฌด์์ ์ง์ง ๋ง์ด ์ฐ๋ ๋๊ตฌ์ผ! ๐ต๏ธ
-- ์ฌ๋ก์ฐ ์ฟผ๋ฆฌ ๋ก๊ทธ ์ค์ (my.cnf ๋๋ ๋์ ์ค์ ) SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1; -- 1์ด ์ด์ ๊ฑธ๋ฆฌ๋ ์ฟผ๋ฆฌ ๊ธฐ๋ก SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log'; SET GLOBAL log_queries_not_using_indexes = 'ON'; -- ์ธ๋ฑ์ค ๋ฏธ์ฌ์ฉ ์ฟผ๋ฆฌ๋ ๊ธฐ๋ก -- ํ์ฌ ์ค์ ํ์ธ SHOW VARIABLES LIKE 'slow_query%'; SHOW VARIABLES LIKE 'long_query_time';
-- mysqldumpslow์ผ๋ก ์ฌ๋ก์ฐ ์ฟผ๋ฆฌ ๋ถ์ # ๊ฐ์ฅ ๋๋ฆฐ ์ฟผ๋ฆฌ 10๊ฐ mysqldumpslow -s t -t 10 /var/log/mysql/slow.log # ๊ฐ์ฅ ๋ง์ด ๋ฐ์ํ ์ฟผ๋ฆฌ 10๊ฐ mysqldumpslow -s c -t 10 /var/log/mysql/slow.log # ํน์ ํจํด ๊ฒ์ mysqldumpslow -s t -t 5 -g "SELECT.*orders" /var/log/mysql/slow.log
Percona Toolkit์
pt-query-digest๋ ์ฌ๋ก์ฐ ์ฟผ๋ฆฌ ๋ถ์์ ๋ํ์์ด์ผ.์ฟผ๋ฆฌ ํจํด๋ณ๋ก ๊ทธ๋ฃนํํด์ ์คํ ํ์, ํ๊ท ์๊ฐ, ์ต๋ ์๊ฐ ๋ฑ์ ๊น๋ํ๊ฒ ๋ณด์ฌ์ค.
์ค๋ฌด์์ DBA๋ค์ด ๊ฐ์ฅ ๋ง์ด ์ฐ๋ ๋๊ตฌ ์ค ํ๋์ผ!
๐ Performance Schema ํ์ฉ
-- ๊ฐ์ฅ ๋ง์ด ์คํ๋ ์ฟผ๋ฆฌ TOP 10
SELECT
DIGEST_TEXT,
COUNT_STAR AS ์คํํ์,
AVG_TIMER_WAIT/1000000000 AS ํ๊ท ์คํ์๊ฐ_์ด,
MAX_TIMER_WAIT/1000000000 AS ์ต๋์คํ์๊ฐ_์ด,
SUM_ROWS_EXAMINED AS ์ด๊ฒ์ฌํ์
FROM performance_schema.events_statements_summary_by_digest
ORDER BY AVG_TIMER_WAIT DESC
LIMIT 10;
-- ์ธ๋ฑ์ค ์ฌ์ฉ ํํฉ ํ์ธ
SELECT
OBJECT_SCHEMA,
OBJECT_NAME,
INDEX_NAME,
COUNT_READ,
COUNT_WRITE
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE OBJECT_SCHEMA = 'your_database'
ORDER BY COUNT_READ DESC;
๐๏ธ ์ค์ ์ธ๋ฑ์ค ์ค๊ณ ์ ๋ต
์, ์ด์ ์ง์ง ์ค์ ์ด์ผ. ์ธ๋ฑ์ค๋ฅผ ์ด๋ป๊ฒ ์ค๊ณํด์ผ ํ๋์ง ๊ตฌ์ฒด์ ์ธ ์ ๋ต์ ์์๋ณด์!
๐ฏ ์นด๋๋๋ฆฌํฐ(Cardinality)๋ฅผ ๊ณ ๋ คํด๋ผ
์นด๋๋๋ฆฌํฐ๋ ์ปฌ๋ผ์ ๊ณ ์ ๊ฐ ๊ฐ์์ผ. ์ธ๋ฑ์ค ์ค๊ณ์์ ๊ฐ์ฅ ์ค์ํ ๊ฐ๋ ์ค ํ๋์ผ.
users ํ ์ด๋ธ์ 100๋ง ๋ช ์ ๋ฐ์ดํฐ๊ฐ ์๋ค๋ฉด:
gender (๋จ/์ฌ) ์นด๋๋๋ฆฌํฐ = 2 โ ์ธ๋ฑ์ค ํจ๊ณผ ๊ฑฐ์ ์์
age (1~100) ์นด๋๋๋ฆฌํฐ = 100 โ ์ธ๋ฑ์ค ํจ๊ณผ ๋ฎ์
city (๋์ ์๋ฐฑ๊ฐ) ์นด๋๋๋ฆฌํฐ = ์๋ฐฑ โ ์ธ๋ฑ์ค ํจ๊ณผ ์ค๊ฐ
email (๊ณ ์ ๊ฐ) ์นด๋๋๋ฆฌํฐ = 100๋ง โ ์ธ๋ฑ์ค ํจ๊ณผ ์ต๊ณ !
โ ์นด๋๋๋ฆฌํฐ๊ฐ ๋์ ์ปฌ๋ผ์ ์ธ๋ฑ์ค๋ฅผ ๊ฑธ์ด์ผ ํจ๊ณผ์ ์ด์ผ!
-- ์ปฌ๋ผ๋ณ ์นด๋๋๋ฆฌํฐ ํ์ธ
SELECT
COUNT(DISTINCT gender) AS gender_cardinality,
COUNT(DISTINCT age) AS age_cardinality,
COUNT(DISTINCT city) AS city_cardinality,
COUNT(DISTINCT email) AS email_cardinality,
COUNT(*) AS total_rows
FROM users;
-- ์ธ๋ฑ์ค ์นด๋๋๋ฆฌํฐ ํ์ธ
SHOW INDEX FROM users;
-- Cardinality ์ปฌ๋ผ ํ์ธ (ํต๊ณ ๊ธฐ๋ฐ ์ถ์ ๊ฐ)
๐ ๋ณตํฉ ์ธ๋ฑ์ค ์ปฌ๋ผ ์์ ๊ฒฐ์ ์ ๋ต
๋ณตํฉ ์ธ๋ฑ์ค์์ ์ปฌ๋ผ ์์๋ ์ฑ๋ฅ์ ์์ฒญ๋ ์ํฅ์ ๋ฏธ์ณ. ์ด๋ค ๊ธฐ์ค์ผ๋ก ์์๋ฅผ ์ ํด์ผ ํ ๊น?
| ์ฐ์ ์์ | ๊ธฐ์ค | ์ค๋ช |
|---|---|---|
| 1 | ๋ฑํธ(=) ์กฐ๊ฑด ์ปฌ๋ผ ๋จผ์ | WHERE status = 'active' AND created_at > '2024-01-01' โ (status, created_at) ์์ |
| 2 | ์นด๋๋๋ฆฌํฐ ๋์ ์ปฌ๋ผ ๋จผ์ | ์ ํ๋๊ฐ ๋์ ์ปฌ๋ผ์ ์์ ๋ฐฐ์นํด ๋น ๋ฅธ ํํฐ๋ง |
| 3 | ORDER BY ์ปฌ๋ผ ํฌํจ | ์ ๋ ฌ ์ปฌ๋ผ์ ์ธ๋ฑ์ค์ ํฌํจํ๋ฉด filesort ์ ๊ฑฐ |
| 4 | SELECT ์ปฌ๋ผ ํฌํจ (์ปค๋ฒ๋ง) | ์์ฃผ ์กฐํํ๋ ์ปฌ๋ผ์ ์ธ๋ฑ์ค์ ํฌํจํด ์ปค๋ฒ๋ง ์ธ๋ฑ์ค ๊ตฌ์ฑ |
-- ์ค์ ์์: ์ด์ปค๋จธ์ค ์ฃผ๋ฌธ ์กฐํ -- ์ฟผ๋ฆฌ ํจํด: -- 1. ํน์ ์ฌ์ฉ์์ ํน์ ์ํ ์ฃผ๋ฌธ์ ์ต์ ์์ผ๋ก ์กฐํ -- 2. SELECT์์ id, user_id, status, total_amount, created_at ์ฌ์ฉ SELECT id, user_id, status, total_amount, created_at FROM orders WHERE user_id = ? AND status = ? ORDER BY created_at DESC LIMIT 20; -- ์ต์ ์ธ๋ฑ์ค ์ค๊ณ: -- user_id (๋ฑํธ ์กฐ๊ฑด, ์นด๋๋๋ฆฌํฐ ๋์) -- status (๋ฑํธ ์กฐ๊ฑด) -- created_at (ORDER BY) -- total_amount (SELECT ์ปค๋ฒ๋ง) CREATE INDEX idx_orders_optimal ON orders(user_id, status, created_at, total_amount); -- EXPLAIN ํ์ธ ์ Extra: Using index ๊ฐ ๋จ๋ฉด ์๋ฒฝ!
๐ซ ์ธ๋ฑ์ค๊ฐ ๋ฌด๋ ฅํ๋๋ ํจํด๋ค
์ธ๋ฑ์ค๋ฅผ ๊ฑธ์ด๋จ๋๋ฐ ์ ์ฐ์ด๋ ๊ฒฝ์ฐ๊ฐ ์์ด. ์ด๋ฐ ํจํด๋ค์ ๊ผญ ํผํด์ผ ํด!
-- โ 1. ์ปฌ๋ผ์ ํจ์ ์ ์ฉ SELECT * FROM users WHERE YEAR(created_at) = 2024; -- โ ๊ฐ์ SELECT * FROM users WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01'; -- โ 2. ์ปฌ๋ผ์ ์ฐ์ฐ ์ ์ฉ SELECT * FROM products WHERE price * 1.1 > 10000; -- โ ๊ฐ์ SELECT * FROM products WHERE price > 10000 / 1.1; -- โ 3. ์์ชฝ ์์ผ๋์นด๋ LIKE SELECT * FROM users WHERE name LIKE '%์ฒ ์'; -- โ ๊ฐ์ (๋ค์ชฝ ์์ผ๋์นด๋๋ ์ธ๋ฑ์ค ์ฌ์ฉ ๊ฐ๋ฅ) SELECT * FROM users WHERE name LIKE '๊น%'; -- โ 4. ์๋ฌต์ ํ์ ๋ณํ -- phone ์ปฌ๋ผ์ด VARCHAR์ธ๋ฐ ์ซ์๋ก ๋น๊ต SELECT * FROM users WHERE phone = 01012345678; -- โ ๊ฐ์ SELECT * FROM users WHERE phone = '01012345678'; -- โ 5. OR ์กฐ๊ฑด (๋ค๋ฅธ ์ปฌ๋ผ) SELECT * FROM users WHERE name = '๊น์ฒ ์' OR email = 'test@test.com'; -- โ ๊ฐ์ (๊ฐ๊ฐ ์ธ๋ฑ์ค๊ฐ ์๋ค๋ฉด UNION ์ฌ์ฉ) SELECT * FROM users WHERE name = '๊น์ฒ ์' UNION SELECT * FROM users WHERE email = 'test@test.com'; -- โ 6. NOT IN, NOT EXISTS (๊ฒฝ์ฐ์ ๋ฐ๋ผ ๋ค๋ฆ) SELECT * FROM users WHERE id NOT IN (SELECT user_id FROM banned_users); -- โ ๊ฐ์ (LEFT JOIN ํ์ฉ) SELECT u.* FROM users u LEFT JOIN banned_users b ON u.id = b.user_id WHERE b.user_id IS NULL;
์ธ๋ฑ์ค๋ ์กฐํ ์ฑ๋ฅ์ ๋์ด์ง๋ง, INSERT/UPDATE/DELETE ์ฑ๋ฅ์ ๋ฎ์ถฐ.
๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝํ ๋๋ง๋ค ๊ด๋ จ๋ ๋ชจ๋ ์ธ๋ฑ์ค๋ฅผ ์ ๋ฐ์ดํธํด์ผ ํ๊ฑฐ๋ .
์ผ๋ฐ์ ์ผ๋ก ํ ์ด๋ธ๋น ์ธ๋ฑ์ค๋ 5~6๊ฐ ์ดํ๋ก ์ ์งํ๋ ๊ฒ ์ข์.
์ฐ์ง ์๋ ์ธ๋ฑ์ค๋ ๊ณผ๊ฐํ ์ญ์ ํด!
๐ง ๊ณ ๊ธ ์ต์ ํ ๊ธฐ๋ฒ๋ค
๐ช ์ธ๋ฑ์ค ํํธ (Index Hint)
MySQL ์ตํฐ๋ง์ด์ ๊ฐ ์๋ชป๋ ์ธ๋ฑ์ค๋ฅผ ์ ํํ ๋ ๊ฐ์ ๋ก ์ธ๋ฑ์ค๋ฅผ ์ง์ ํ ์ ์์ด.
-- ํน์ ์ธ๋ฑ์ค ๊ฐ์ ์ฌ์ฉ SELECT * FROM orders FORCE INDEX (idx_status_created) WHERE status = 'pending' AND created_at > '2024-01-01'; -- ํน์ ์ธ๋ฑ์ค ์ฌ์ฉ ๊ถ์ฅ (์ตํฐ๋ง์ด์ ๊ฐ ๋ฌด์ํ ์ ์์) SELECT * FROM orders USE INDEX (idx_status_created) WHERE status = 'pending'; -- ํน์ ์ธ๋ฑ์ค ์ฌ์ฉ ๊ธ์ง SELECT * FROM orders IGNORE INDEX (idx_old_index) WHERE status = 'pending'; -- ์ฃผ์: ์ธ๋ฑ์ค ํํธ๋ ์ตํ์ ์๋จ! -- ์ตํฐ๋ง์ด์ ๋ฅผ ๋ฏฟ๋, ํต๊ณ๊ฐ ์๋ชป๋์ ๋๋ง ์ฌ์ฉํด.
๐ ํต๊ณ ์ ๋ณด ์ ๋ฐ์ดํธ
-- ํ ์ด๋ธ ํต๊ณ ์ ๋ฐ์ดํธ (์ตํฐ๋ง์ด์ ๊ฐ ์ฌ๋ฐ๋ฅธ ์คํ๊ณํ ์๋ฆฝํ๋๋ก) ANALYZE TABLE orders; ANALYZE TABLE users; -- ์ฌ๋ฌ ํ ์ด๋ธ ํ๋ฒ์ ANALYZE TABLE orders, users, products; -- InnoDB ํต๊ณ ์ค์ ํ์ธ SHOW VARIABLES LIKE 'innodb_stats%'; -- ํต๊ณ ์ํ ํ์ด์ง ์ ๋๋ฆฌ๊ธฐ (๋ ์ ํํ ํต๊ณ) SET GLOBAL innodb_stats_persistent_sample_pages = 20; -- ๊ธฐ๋ณธ๊ฐ 20 -- ํฐ ํ ์ด๋ธ์ด๋ผ๋ฉด ๋ ๋๊ฒ ์ค์ ๊ณ ๋ ค
๐ ํํฐ์ ๋๊ณผ ์ธ๋ฑ์ค
๋์ฉ๋ ํ ์ด๋ธ์์๋ ํํฐ์ ๋๊ณผ ์ธ๋ฑ์ค๋ฅผ ํจ๊ป ํ์ฉํ๋ฉด ์์ฒญ๋ ์ฑ๋ฅ ํฅ์์ ๋ณผ ์ ์์ด!
-- ๋ ์ง ๊ธฐ๋ฐ ํํฐ์
๋ ์์
CREATE TABLE orders_partitioned (
id BIGINT AUTO_INCREMENT,
user_id BIGINT NOT NULL,
status VARCHAR(20) NOT NULL,
total_amount DECIMAL(10,2),
created_at DATETIME NOT NULL,
PRIMARY KEY (id, created_at), -- ํํฐ์
ํค๊ฐ PK์ ํฌํจ๋์ด์ผ ํจ
INDEX idx_user_status (user_id, status)
)
PARTITION BY RANGE (YEAR(created_at)) (
PARTITION p2022 VALUES LESS THAN (2023),
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
-- ํํฐ์
ํ๋ฃจ๋ ํ์ธ (ํน์ ํํฐ์
๋ง ์ค์บ)
EXPLAIN SELECT * FROM orders_partitioned
WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';
-- partitions ์ปฌ๋ผ์์ p2024๋ง ํ์๋๋ฉด ์ฑ๊ณต!
๐๏ธ ICP (Index Condition Pushdown)
MySQL 5.6๋ถํฐ ๋์ ๋ ์ต์ ํ ๊ธฐ๋ฒ์ด์ผ.
๊ธฐ์กด์๋ ์ธ๋ฑ์ค๋ก ํ์ ์ฐพ์ ํ ์คํ ๋ฆฌ์ง ์์ง์์ MySQL ์๋ฒ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์์ WHERE ์กฐ๊ฑด์ ์ฒดํฌํ์ด.
ICP๋ WHERE ์กฐ๊ฑด ์ฒดํฌ๋ฅผ ์คํ ๋ฆฌ์ง ์์ง ๋ ๋ฒจ์์ ๋จผ์ ์ํํด์ ๋ถํ์ํ ๋ฐ์ดํฐ ์ ์ก์ ์ค์ฌ.
EXPLAIN Extra์
Using index condition์ด ๋จ๋ฉด ICP๊ฐ ์ ์ฉ๋ ๊ฑฐ์ผ! โ
-- ICP ์ค์ ํ์ธ ๋ฐ ์ ์ด SHOW VARIABLES LIKE 'optimizer_switch'; -- index_condition_pushdown=on ํ์ธ -- ICP ๋นํ์ฑํ (ํ ์คํธ์ฉ) SET optimizer_switch = 'index_condition_pushdown=off'; -- ๋ค์ ํ์ฑํ SET optimizer_switch = 'index_condition_pushdown=on';
๐ JOIN ์ต์ ํ์ ์ธ๋ฑ์ค
JOIN ์ฟผ๋ฆฌ์์ ์ธ๋ฑ์ค ์ค๊ณ๋ ๋์ฑ ์ค์ํด. ์๋ชปํ๋ฉด ์ฑ๋ฅ์ด ๊ธฐํ๊ธ์์ ์ผ๋ก ๋๋น ์ง ์ ์๊ฑฐ๋ !
-- ๋ฌธ์ ์๋ JOIN ์ฟผ๋ฆฌ SELECT u.name, o.total_amount, p.name AS product_name FROM users u JOIN orders o ON u.id = o.user_id JOIN order_items oi ON o.id = oi.order_id JOIN products p ON oi.product_id = p.id WHERE u.city = '์์ธ' AND o.status = 'completed' AND o.created_at >= '2024-01-01'; -- ํ์ํ ์ธ๋ฑ์ค๋ค: -- users: city ์ปฌ๋ผ ์ธ๋ฑ์ค CREATE INDEX idx_users_city ON users(city); -- orders: user_id + status + created_at ๋ณตํฉ ์ธ๋ฑ์ค CREATE INDEX idx_orders_user_status_date ON orders(user_id, status, created_at); -- order_items: order_id ์ธ๋ฑ์ค (FK ์ธ๋ฑ์ค) CREATE INDEX idx_order_items_order ON order_items(order_id); -- products: PK๊ฐ ์์ผ๋ฉด ์ถฉ๋ถ (id๋ก ์กฐ์ธ) -- JOIN ์์ ์ต์ ํ: ์์ ๊ฒฐ๊ณผ์ ์ด ๋๋ผ์ด๋น ํ ์ด๋ธ์ด ๋๋๋ก -- MySQL ์ตํฐ๋ง์ด์ ๊ฐ ์๋์ผ๋ก ์ต์ ํํ์ง๋ง, -- STRAIGHT_JOIN์ผ๋ก ๊ฐ์ ์ง์ ๋ ๊ฐ๋ฅ
1 JOIN ์กฐ๊ฑด ์ปฌ๋ผ์๋ ๋ฐ๋์ ์ธ๋ฑ์ค๊ฐ ์์ด์ผ ํด.
2 ๋๋ผ์ด๋น ํ ์ด๋ธ(๊ฒฐ๊ณผ์ ์ด ์์ ์ชฝ)์ ๋จผ์ ์ฒ๋ฆฌํด.
3 ON ์ ์ ์ปฌ๋ผ ํ์ ์ด ์ผ์นํด์ผ ์ธ๋ฑ์ค๊ฐ ์ ๋๋ก ์ฌ์ฉ๋ผ.
4 ๊ฐ๋ฅํ๋ฉด JOIN ์ ์ WHERE๋ก ๋ฐ์ดํฐ๋ฅผ ์ต๋ํ ์ค์ฌ.
๐ ๏ธ ์ค๋ฌด์์ ์์ฃผ ์ฐ๋ ์ฑ๋ฅ ๋ถ์ ์ฟผ๋ฆฌ ๋ชจ์
์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์ค์ ์๋น์ค๋ฅผ ์ด์ํ ๋ ์ ์ฉํ ์ฑ๋ฅ ๋ถ์ ์ฟผ๋ฆฌ๋ค์ ๋ชจ์๋ดค์ด. ๋ถ๋งํฌํด๋๊ณ ํ์ํ ๋ ๊บผ๋ด ์จ!
๐ ๋ฏธ์ฌ์ฉ ์ธ๋ฑ์ค ์ฐพ๊ธฐ
-- ํ ๋ฒ๋ ์ฌ์ฉ๋์ง ์์ ์ธ๋ฑ์ค ์ฐพ๊ธฐ (MySQL ์ฌ์์ ํ๋ถํฐ ์นด์ดํธ)
SELECT
t.TABLE_SCHEMA,
t.TABLE_NAME,
s.INDEX_NAME,
s.COLUMN_NAME,
s.SEQ_IN_INDEX,
t2.COUNT_READ
FROM information_schema.STATISTICS s
JOIN information_schema.TABLES t
ON s.TABLE_SCHEMA = t.TABLE_SCHEMA AND s.TABLE_NAME = t.TABLE_NAME
LEFT JOIN performance_schema.table_io_waits_summary_by_index_usage t2
ON s.TABLE_SCHEMA = t2.OBJECT_SCHEMA
AND s.TABLE_NAME = t2.OBJECT_NAME
AND s.INDEX_NAME = t2.INDEX_NAME
WHERE t.TABLE_SCHEMA = 'your_database'
AND s.INDEX_NAME != 'PRIMARY'
AND (t2.COUNT_READ = 0 OR t2.COUNT_READ IS NULL)
ORDER BY t.TABLE_NAME, s.INDEX_NAME;
๐ ํ ์ด๋ธ ํฌ๊ธฐ ๋ฐ ์ธ๋ฑ์ค ํฌ๊ธฐ ํ์ธ
SELECT
TABLE_NAME AS ํ
์ด๋ธ๋ช
,
ROUND(DATA_LENGTH/1024/1024, 2) AS ๋ฐ์ดํฐํฌ๊ธฐ_MB,
ROUND(INDEX_LENGTH/1024/1024, 2) AS ์ธ๋ฑ์คํฌ๊ธฐ_MB,
ROUND((DATA_LENGTH + INDEX_LENGTH)/1024/1024, 2) AS ์ ์ฒดํฌ๊ธฐ_MB,
TABLE_ROWS AS ์์ํ์,
ROUND(INDEX_LENGTH/DATA_LENGTH * 100, 1) AS ์ธ๋ฑ์ค๋น์จ_ํผ์ผํธ
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'your_database'
ORDER BY (DATA_LENGTH + INDEX_LENGTH) DESC;
์ธ๋ฑ์ค ๋น์จ์ด 100%๋ฅผ ๋์ผ๋ฉด ์ธ๋ฑ์ค๊ฐ ๋๋ฌด ๋ง๋ค๋ ์ ํธ์ผ.
๋ถํ์ํ ์ธ๋ฑ์ค๋ฅผ ์ ๋ฆฌํด์ ์ ์ฅ ๊ณต๊ฐ๊ณผ ์ฐ๊ธฐ ์ฑ๋ฅ์ ๊ฐ์ ํด์ผ ํด!
๐ ์ค๋ณต ์ธ๋ฑ์ค ์ฐพ๊ธฐ
-- ์ค๋ณต ๋๋ ๋ถํ์ํ ์ธ๋ฑ์ค ์ฐพ๊ธฐ
-- (A) ์ธ๋ฑ์ค์ (A, B) ์ธ๋ฑ์ค๊ฐ ์์ผ๋ฉด (A) ์ธ๋ฑ์ค๋ ์ค๋ณต!
SELECT
a.TABLE_NAME,
a.INDEX_NAME AS ์ค๋ณต๊ฐ๋ฅ์ธ๋ฑ์ค,
a.COLUMN_NAME,
b.INDEX_NAME AS ํฌํจํ๋์ธ๋ฑ์ค
FROM information_schema.STATISTICS a
JOIN information_schema.STATISTICS b
ON a.TABLE_SCHEMA = b.TABLE_SCHEMA
AND a.TABLE_NAME = b.TABLE_NAME
AND a.COLUMN_NAME = b.COLUMN_NAME
AND a.SEQ_IN_INDEX = b.SEQ_IN_INDEX
AND a.INDEX_NAME != b.INDEX_NAME
WHERE a.TABLE_SCHEMA = 'your_database'
AND a.SEQ_IN_INDEX = 1
ORDER BY a.TABLE_NAME, a.INDEX_NAME;
โฑ๏ธ ํ์ฌ ์คํ ์ค์ธ ์ฟผ๋ฆฌ ๋ชจ๋ํฐ๋ง
-- ํ์ฌ ์คํ ์ค์ธ ์ฟผ๋ฆฌ ๋ชฉ๋ก (5์ด ์ด์ ์คํ ์ค์ธ ๊ฒ)
SELECT
ID,
USER,
HOST,
DB,
COMMAND,
TIME AS ์คํ์๊ฐ_์ด,
STATE,
LEFT(INFO, 200) AS ์ฟผ๋ฆฌ๋ด์ฉ
FROM information_schema.PROCESSLIST
WHERE COMMAND != 'Sleep'
AND TIME > 5
ORDER BY TIME DESC;
-- ํน์ ์ฟผ๋ฆฌ ๊ฐ์ ์ข
๋ฃ (ํ์์)
KILL QUERY [ํ๋ก์ธ์คID];
-- ์ ๊ธ ๋๊ธฐ ์ค์ธ ์ฟผ๋ฆฌ ํ์ธ
SELECT
r.trx_id AS ๋๊ธฐ์ค์ธํธ๋์ญ์
,
r.trx_mysql_thread_id AS ๋๊ธฐ์ค๋ ๋,
b.trx_id AS ์ ๊ธ๋ณด์ ํธ๋์ญ์
,
b.trx_mysql_thread_id AS ์ ๊ธ์ค๋ ๋,
b.trx_query AS ์ ๊ธ๋ณด์ ์ฟผ๋ฆฌ
FROM information_schema.INNODB_LOCK_WAITS w
JOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id
JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id;
๐ ์ธ๋ฑ์ค ์ค๊ณ ์ฒดํฌ๋ฆฌ์คํธ
์ธ๋ฑ์ค๋ฅผ ์ค๊ณํ๊ฑฐ๋ ๋ฆฌ๋ทฐํ ๋ ์ด ์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ํ์ฉํด๋ด! ์ฌ๋ฅ๋ท์์ DB ๊ด๋ จ ์ฌ๋ฅ์ ๊ฑฐ๋ํ๋ ๋ถ๋ค๋ ์ด ๊ธฐ์ค์ผ๋ก ์ฝ๋ ๋ฆฌ๋ทฐ๋ฅผ ํ๋ฉด ์ข์ ๊ฑฐ์ผ. ๐
๐ก MySQL 8.0 ์ ๊ธฐ๋ฅ โ ์ธ๋ฑ์ค ๊ด๋ จ
๐ ๋ณด์ด์ง ์๋ ์ธ๋ฑ์ค (Invisible Index)
-- MySQL 8.0์ ํ์ ์ ์ธ ๊ธฐ๋ฅ! -- ์ธ๋ฑ์ค๋ฅผ ์ญ์ ํ์ง ์๊ณ ์ตํฐ๋ง์ด์ ์๊ฒ "์ ๋ณด์ด๊ฒ" ํ ์ ์์ด -- ์ธ๋ฑ์ค๋ฅผ invisible๋ก ์ค์ (์ตํฐ๋ง์ด์ ๊ฐ ์ฌ์ฉ ์ ํจ) ALTER TABLE orders ALTER INDEX idx_old_index INVISIBLE; -- ์ด ์ํ์์ ์ฑ๋ฅ ํ ์คํธ โ ๋ฌธ์ ์์ผ๋ฉด ์ค์ ์ญ์ DROP INDEX idx_old_index ON orders; -- ๋ค์ visible๋ก ๋ณต์ (์ญ์ ์ ๋กค๋ฐฑ ๊ฐ๋ฅ!) ALTER TABLE orders ALTER INDEX idx_old_index VISIBLE; -- ํ์ฌ ์ธ๋ฑ์ค visibility ํ์ธ SELECT INDEX_NAME, IS_VISIBLE FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = 'your_database' AND TABLE_NAME = 'orders' GROUP BY INDEX_NAME, IS_VISIBLE;
์ธ๋ฑ์ค ์ญ์ ๋ ๋๋๋ฆฌ๊ธฐ ์ด๋ ต๊ณ ์ฌ์์ฑ์ ์๊ฐ์ด ๋ง์ด ๊ฑธ๋ ค.
Invisible Index๋ฅผ ํ์ฉํ๋ฉด "์ด ์ธ๋ฑ์ค ์์ ๋ ๋๋?"๋ฅผ ์์ ํ๊ฒ ํ ์คํธํ ์ ์์ด.
ํ๋ก๋์ ํ๊ฒฝ์์ ์ธ๋ฑ์ค ์ ๋ฆฌํ ๋ ์ ๋ง ์ ์ฉํ ๊ธฐ๋ฅ์ด์ผ!
๐ ๊ฐ์ ์ธ๋ฑ์ค ์์ (Descending Index)
-- MySQL 8.0๋ถํฐ ๋ด๋ฆผ์ฐจ์ ์ธ๋ฑ์ค ์ง์ CREATE INDEX idx_created_desc ON orders(created_at DESC); -- ์ด๋ฐ ์ฟผ๋ฆฌ์์ filesort ์์ด ์ฒ๋ฆฌ ๊ฐ๋ฅ SELECT * FROM orders ORDER BY created_at DESC LIMIT 10; -- ๋ณตํฉ ์ธ๋ฑ์ค์์ ํผํฉ ์ ๋ ฌ ๋ฐฉํฅ๋ ๊ฐ๋ฅ CREATE INDEX idx_user_date ON orders(user_id ASC, created_at DESC); -- ์ด ์ฟผ๋ฆฌ๊ฐ ์ด์ filesort ์์ด ์ฒ๋ฆฌ๋จ! SELECT * FROM orders WHERE user_id = 123 ORDER BY user_id ASC, created_at DESC;
๐ Skip Scan ์ต์ ํ
-- MySQL 8.0์์ ๋์ ๋ Skip Scan -- ๋ณตํฉ ์ธ๋ฑ์ค์ ์ฒซ ๋ฒ์งธ ์ปฌ๋ผ์ด WHERE์ ์์ด๋ ์ธ๋ฑ์ค ํ์ฉ ๊ฐ๋ฅ! -- (gender, age) ๋ณตํฉ ์ธ๋ฑ์ค๊ฐ ์์ ๋ CREATE INDEX idx_gender_age ON users(gender, age); -- ๊ธฐ์กด์๋ ์ด ์ฟผ๋ฆฌ๊ฐ ์ธ๋ฑ์ค๋ฅผ ๋ชป ์ผ์ง๋ง -- MySQL 8.0 Skip Scan์ผ๋ก ์ธ๋ฑ์ค ํ์ฉ ๊ฐ๋ฅ! SELECT * FROM users WHERE age > 25; -- EXPLAIN์์ ํ์ธ -- Extra: Using index for skip scan -- ๋จ, gender์ ์นด๋๋๋ฆฌํฐ๊ฐ ๋ฎ์ ๋ ํจ๊ณผ์ -- (gender๊ฐ '๋จ'/'์ฌ' 2๊ฐ์ง๋ผ๋ฉด ๊ฐ๊ฐ์ ๋ํด age ๋ฒ์ ํ์)
๐ฎ ์ค์ ์๋๋ฆฌ์ค: ์ด์ปค๋จธ์ค DB ์ต์ ํ
์ค์ ์๋น์ค์์ ์์ฃผ ๋ง๋๋ ์๋๋ฆฌ์ค๋ก ์ข ํฉ ์ ๋ฆฌํด๋ณผ๊ฒ. ์ด์ปค๋จธ์ค ํ๋ซํผ์ ์์๋ก ๋ค์ด๋ณผ๊ฒ!
-- ์๋๋ฆฌ์ค: ์ํ ๊ฒ์ + ํํฐ๋ง + ์ ๋ ฌ ์ฟผ๋ฆฌ ์ต์ ํ -- ๋ฌธ์ ์ฟผ๋ฆฌ (๋๋ฆผ) SELECT p.id, p.name, p.price, p.stock, c.name AS category_name FROM products p JOIN categories c ON p.category_id = c.id WHERE p.status = 'active' AND p.price BETWEEN 10000 AND 50000 AND p.category_id = 5 ORDER BY p.created_at DESC LIMIT 20 OFFSET 0; -- Step 1: EXPLAIN์ผ๋ก ํํฉ ํ์ EXPLAIN SELECT p.id, p.name, p.price, p.stock, c.name AS category_name FROM products p JOIN categories c ON p.category_id = c.id WHERE p.status = 'active' AND p.price BETWEEN 10000 AND 50000 AND p.category_id = 5 ORDER BY p.created_at DESC LIMIT 20 OFFSET 0; -- ๊ฒฐ๊ณผ: products ํ ์ด๋ธ type=ALL, Using filesort ๋ฐ์ -- Step 2: ์ธ๋ฑ์ค ์ค๊ณ -- ์กฐ๊ฑด ๋ถ์: -- - category_id = 5 (๋ฑํธ, ์นด๋๋๋ฆฌํฐ ์ค๊ฐ) -- - status = 'active' (๋ฑํธ, ์นด๋๋๋ฆฌํฐ ๋ฎ์) -- - price BETWEEN (๋ฒ์) -- - ORDER BY created_at DESC -- ์ต์ ์ธ๋ฑ์ค (๋ฑํธ ์กฐ๊ฑด ๋จผ์ , ๋ฒ์ ์กฐ๊ฑด ๋ค, ์ ๋ ฌ ์ปฌ๋ผ ๋ง์ง๋ง) CREATE INDEX idx_products_search ON products(category_id, status, price, created_at); -- ์ปค๋ฒ๋ง ์ธ๋ฑ์ค ๋ฒ์ (JOIN ์์ด ์ฒ๋ฆฌํ๋ ค๋ฉด) CREATE INDEX idx_products_covering ON products(category_id, status, price, created_at, id, name, stock); -- Step 3: ๊ฐ์ ํ EXPLAIN ํ์ธ -- type: range, Extra: Using index condition (๋๋ Using index) -- Step 4: ํ์ด์ง๋ค์ด์ ์ต์ ํ (OFFSET์ด ํฌ๋ฉด ๋๋ ค์ง) -- ๋ฌธ์ : LIMIT 20 OFFSET 10000 โ 10020๊ฐ ์ฝ๊ณ 10000๊ฐ ๋ฒ๋ฆผ -- ํด๊ฒฐ: ์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์ SELECT p.id, p.name, p.price, p.stock FROM products p WHERE p.status = 'active' AND p.category_id = 5 AND p.price BETWEEN 10000 AND 50000 AND p.created_at < '2024-06-01 12:00:00' -- ์ด์ ํ์ด์ง ๋ง์ง๋ง created_at ORDER BY p.created_at DESC LIMIT 20;
OFFSET ๋ฐฉ์์ ํ์ด์ง๊ฐ ๋ค๋ก ๊ฐ์๋ก ๋๋ ค์ ธ. (OFFSET 100000์ด๋ฉด 100020๊ฐ๋ฅผ ์ฝ๊ณ 100000๊ฐ๋ฅผ ๋ฒ๋ฆผ)
์ปค์ ๊ธฐ๋ฐ์ ํญ์ ์ธ๋ฑ์ค๋ฅผ ํตํด ์์์ ์ ์ฐพ์์ ํ์ด์ง ์์น์ ๋ฌด๊ดํ๊ฒ ์ผ์ ํ ์ฑ๋ฅ์ ์ ์งํด.
SNS ํผ๋, ๋ฌดํ ์คํฌ๋กค ๊ตฌํ์ ํนํ ์ ํฉํด!
๐ฏ ํต์ฌ ์ ๋ฆฌ โ ์ค๋ ๋ฐฐ์ด ๊ฒ๋ค
1 B+ Tree ๊ตฌ์กฐ ์ดํด โ MySQL InnoDB์ ์ธ๋ฑ์ค๋ B+ Tree. ๋ฆฌํ ๋ ธ๋ ์ฐ๊ฒฐ๋ก ๋ฒ์ ๊ฒ์์ด ๋น ๋ฆ.
2 ํด๋ฌ์คํฐ๋ vs ์ธ์ปจ๋๋ฆฌ โ PK๋ ํด๋ฌ์คํฐ๋, ๋๋จธ์ง๋ ์ธ์ปจ๋๋ฆฌ. PK ํฌ๊ธฐ๊ฐ ์ธ์ปจ๋๋ฆฌ ์ธ๋ฑ์ค ํฌ๊ธฐ์ ์ํฅ.
3 ๋ณตํฉ ์ธ๋ฑ์ค ์ผ์ชฝ ์ ๋์ฌ ๊ท์น โ (A,B,C) ์ธ๋ฑ์ค๋ A, A+B, A+B+C ์กฐํฉ๋ง ์ฌ์ฉ ๊ฐ๋ฅ.
4 ์ปค๋ฒ๋ง ์ธ๋ฑ์ค โ SELECT ์ปฌ๋ผ์ด ๋ชจ๋ ์ธ๋ฑ์ค์ ์์ผ๋ฉด ํ ์ด๋ธ ์ ๊ทผ ๋ถํ์. Extra: Using index.
5 EXPLAIN ๋ถ์ โ type ์ปฌ๋ผ์ด ํต์ฌ. ALL/index๋ ๊ฐ์ ๋์. Using filesort/temporary ์ ๊ฑฐ ๋ชฉํ.
6 ์ธ๋ฑ์ค ๋ฌด๋ ฅํ ํจํด ํํผ โ ํจ์ ์ ์ฉ, ์์ชฝ ์์ผ๋์นด๋, ํ์ ๋ถ์ผ์น๋ ์ธ๋ฑ์ค๋ฅผ ๋ชป ์.
7 ์ฌ๋ก์ฐ ์ฟผ๋ฆฌ ๋ก๊ทธ โ ๋๋ฆฐ ์ฟผ๋ฆฌ๋ฅผ ์๋์ผ๋ก ์ฐพ์์ฃผ๋ ํ์ ๋๊ตฌ. long_query_time ์ค์ .
8 MySQL 8.0 ์ ๊ธฐ๋ฅ โ Invisible Index, Descending Index, Skip Scan, ํจ์ ๊ธฐ๋ฐ ์ธ๋ฑ์ค ํ์ฉ.
์ฌ๊ธฐ๊น์ง MySQL ์ธ๋ฑ์ค ์ค๊ณ์ ์ฟผ๋ฆฌ ์ฑ๋ฅ ๋ถ์์ ๋ํด ์ญ ๋ฌ๋ ค์์ด! ๐ค
์ฒ์์ ๋ณต์กํด ๋ณด์ฌ๋, ๊ฒฐ๊ตญ ํต์ฌ์ ๊ฐ๋จํด. "์ตํฐ๋ง์ด์ ๊ฐ ์ธ๋ฑ์ค๋ฅผ ์ ํ ์ ์๋๋ก ์ฟผ๋ฆฌ์ ์ธ๋ฑ์ค๋ฅผ ์ค๊ณํ๋ ๊ฒ"์ด์ผ.
EXPLAIN์ ์ต๊ด์ฒ๋ผ ์ฐ๊ณ , ์ฌ๋ก์ฐ ์ฟผ๋ฆฌ ๋ก๊ทธ๋ฅผ ์ ๊ธฐ์ ์ผ๋ก ํ์ธํ๊ณ , ์ธ๋ฑ์ค๋ฅผ ์ฃผ๊ธฐ์ ์ผ๋ก ์ ๋ฆฌํ๋ ๊ฒ โ ์ด ์ธ ๊ฐ์ง๋ง ๊พธ์คํ ํด๋ DB ์ฑ๋ฅ์ ํ์คํ ๋ฌ๋ผ์ ธ.
DB ํ๋์ด๋ ์ฟผ๋ฆฌ ์ต์ ํ์ ๋ํด ๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด, ์ฌ๋ฅ๋ท์์ DB ์ ๋ฌธ๊ฐ๋ค์ ์ฌ๋ฅ์ ํ์ฉํด๋ณด๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ. ์ค๋ฌด ๊ฒฝํ์ด ํ๋ถํ DBA๋ ๋ฐฑ์๋ ๊ฐ๋ฐ์์๊ฒ ์ง์ ์ฝ๋ ๋ฆฌ๋ทฐ๋ฅผ ๋ฐ์ผ๋ฉด ํจ์ฌ ๋น ๋ฅด๊ฒ ์ฑ์ฅํ ์ ์๊ฑฐ๋ ! ๐
์, ์ด์ ๋๋ฆฐ ์ฟผ๋ฆฌ ๋ณด๋ฉด ๋ ์ด์ ๋๋ ต์ง ์์ง? EXPLAIN ์ผ๊ณ ๋ถ์ ์์ํด๋ด! ๐ช
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.