์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿ—‚๏ธ MySQL ์ธ๋ฑ์Šค ์„ค๊ณ„์™€ ์ฟผ๋ฆฌ ์„ฑ๋Šฅ ๋ถ„์„ ์™„์ „ ์ •๋ณต ๊ฐ€์ด๋“œ
DB / ์„œ๋ฒ„ ยท ํ”„๋กœ๊ทธ๋žจ๊ฐœ๋ฐœ

๐Ÿ—‚๏ธ MySQL ์ธ๋ฑ์Šค ์„ค๊ณ„์™€ ์ฟผ๋ฆฌ ์„ฑ๋Šฅ ๋ถ„์„ ์™„์ „ ์ •๋ณต ๊ฐ€์ด๋“œ

๋А๋ฆฐ ์ฟผ๋ฆฌ์— ์ง€์ณ์žˆ๋Š” ๋‹น์‹ ์„ ์œ„ํ•œ, ์ฐ ์‹ค์ „ ํŠœ๋‹ ์ด์•ผ๊ธฐ ๐Ÿš€

์•ผ, ์†”์งํžˆ ๋งํ•ด๋ด. ๊ฐœ๋ฐœํ•˜๋‹ค๊ฐ€ ์ฟผ๋ฆฌ ํ•˜๋‚˜ ๋‚ ๋ ธ๋Š”๋ฐ ๋กœ๋”ฉ์ด 5์ดˆ, 10์ดˆ์”ฉ ๊ฑธ๋ ค์„œ ๋ฉ˜ํƒˆ ๋‚˜๊ฐ„ ์  ์žˆ์ง€ ์•Š์•„? ๐Ÿ˜…
๊ทธ ์ˆœ๊ฐ„ "์•„ ์ธ๋ฑ์Šค ์ข€ ๊ฑธ์–ด์•ผ ํ•˜๋‚˜..." ํ•˜๊ณ  ๋ง‰์—ฐํ•˜๊ฒŒ ์ƒ๊ฐ๋งŒ ํ•˜๋‹ค๊ฐ€ ๊ฒฐ๊ตญ ๊ทธ๋ƒฅ ๋„˜์–ด๊ฐ„ ๊ฒฝํ—˜, ๋‹ค๋“ค ํ•œ ๋ฒˆ์ฏค์€ ์žˆ์„ ๊ฑฐ์•ผ.

์˜ค๋Š˜์€ ๊ทธ ๋ง‰์—ฐํ•จ์„ ์™„์ „ํžˆ ๋‚ ๋ ค๋ฒ„๋ฆด ๊ฑฐ์•ผ. MySQL ์ธ๋ฑ์Šค์˜ ์›๋ฆฌ๋ถ€ํ„ฐ ์„ค๊ณ„ ์ „๋žต, ์ฟผ๋ฆฌ ์„ฑ๋Šฅ ๋ถ„์„ ๋ฐฉ๋ฒ•๊นŒ์ง€ โ€” ์นœ๊ตฌํ•œํ…Œ ์„ค๋ช…ํ•˜๋“ฏ ์ญ‰ ํ’€์–ด๋ณผ๊ฒŒ. ์ปคํ”ผ ํ•œ ์ž” ๋“ค๊ณ  ํŽธํ•˜๊ฒŒ ์ฝ์–ด๋ด! โ˜•

์ธ๋ฑ์Šค ์—†์„ ๋•Œ vs ์žˆ์„ ๋•Œ โ€” ์„ฑ๋Šฅ ์ฐจ์ด ํ•œ๋ˆˆ์—! โŒ ์ธ๋ฑ์Šค ์—†์Œ (Full Table Scan) ํ–‰ 1 โ†’ ๊ฒ€์‚ฌ ์ค‘... ๐Ÿ˜“ ํ–‰ 2 โ†’ ๊ฒ€์‚ฌ ์ค‘... ๐Ÿ˜“ ํ–‰ 3 โ†’ ๊ฒ€์‚ฌ ์ค‘... ๐Ÿ˜“ ํ–‰ 4 โ†’ ๊ฒ€์‚ฌ ์ค‘... ๐Ÿ˜“ ํ–‰ N โ†’ ๊ฒ€์‚ฌ ์ค‘... ๐Ÿ˜“ โฑ ์ˆ˜๋ฐฑ๋งŒ ๊ฑด = ์ˆ˜์ดˆ ๋Œ€๊ธฐ O(N) ์„ ํ˜• ํƒ์ƒ‰ โšก ์ธ๋ฑ์Šค ์ถ”๊ฐ€! โœ… ์ธ๋ฑ์Šค ์žˆ์Œ (B-Tree ํƒ์ƒ‰) ๋ฃจํŠธ ๋…ธ๋“œ ์ค‘๊ฐ„ ๋…ธ๋“œ ์ค‘๊ฐ„ ๋…ธ๋“œ ๋ฆฌํ”„ ๋ฆฌํ”„ ๐ŸŽฏ ์ฆ‰์‹œ ๋ชฉํ‘œ ํ–‰ ํƒ์ƒ‰! O(log N) ๋กœ๊ทธ ํƒ์ƒ‰ ์ˆ˜๋ฐฑ๋งŒ ๊ฑด๋„ ๋ฐ€๋ฆฌ์ดˆ ๋‹จ์œ„ โœจ

๐ŸŒณ ์ธ๋ฑ์Šค๊ฐ€ ๋ญ”์ง€ ์ œ๋Œ€๋กœ ์•Œ๊ณ  ๊ฐ€์ž

์ธ๋ฑ์Šค๋ฅผ ํ•œ ๋งˆ๋””๋กœ ํ‘œํ˜„ํ•˜๋ฉด? "์ฑ…์˜ ๋ชฉ์ฐจ"์•ผ.
๋‘๊บผ์šด ์ฑ…์—์„œ ํŠน์ • ๋‚ด์šฉ์„ ์ฐพ์„ ๋•Œ, ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ๋‹ค ์ฝ๋Š” ์‚ฌ๋žŒ์€ ์—†์ž–์•„. ๋ชฉ์ฐจ ๋ณด๊ณ  ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ ํ™•์ธํ•˜๊ณ  ๋ฐ”๋กœ ๊ทธ ํŽ˜์ด์ง€๋กœ ๊ฐ€์ง€.
MySQL ์ธ๋ฑ์Šค๋„ ๋˜‘๊ฐ™์•„. ๋ฐ์ดํ„ฐ๋ฅผ ๋น ๋ฅด๊ฒŒ ์ฐพ๊ธฐ ์œ„ํ•œ ๋ณ„๋„์˜ ์ž๋ฃŒ๊ตฌ์กฐ์•ผ.

๐Ÿ“š MySQL์ด ๊ธฐ๋ณธ์œผ๋กœ ์“ฐ๋Š” B-Tree ์ธ๋ฑ์Šค

MySQL InnoDB ์—”์ง„์˜ ๊ธฐ๋ณธ ์ธ๋ฑ์Šค ๊ตฌ์กฐ๋Š” B+ Tree (B-Plus Tree)์•ผ.
B-Tree ๊ณ„์—ด์ด๋ผ "B-Tree ์ธ๋ฑ์Šค"๋ผ๊ณ  ๋งŽ์ด ๋ถ€๋ฅด๋Š”๋ฐ, ์ •ํ™•ํžˆ๋Š” B+ Tree์•ผ. ์ฐจ์ด์ ์ด ๋ญ๋ƒ๊ณ ?

๐Ÿ” B-Tree vs B+ Tree ํ•ต์‹ฌ ์ฐจ์ด

B-Tree: ๋ชจ๋“  ๋…ธ๋“œ(๋ฃจํŠธ, ์ค‘๊ฐ„, ๋ฆฌํ”„)์— ์‹ค์ œ ๋ฐ์ดํ„ฐ ํฌ์ธํ„ฐ๊ฐ€ ์žˆ์–ด.
B+ Tree: ๋ฆฌํ”„ ๋…ธ๋“œ์—๋งŒ ์‹ค์ œ ๋ฐ์ดํ„ฐ ํฌ์ธํ„ฐ๊ฐ€ ์žˆ๊ณ , ๋ฆฌํ”„ ๋…ธ๋“œ๋“ค์ด ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ๋กœ ์ด์–ด์ ธ ์žˆ์–ด.

โ†’ ๋•๋ถ„์— ๋ฒ”์œ„ ๊ฒ€์ƒ‰(BETWEEN, >, < ๋“ฑ)์ด ์—„์ฒญ ๋น ๋ฆ„! ๋ฆฌํ”„ ๋…ธ๋“œ๋งŒ ์ˆœ์„œ๋Œ€๋กœ ํ›‘์œผ๋ฉด ๋˜๊ฑฐ๋“ . ๐ŸŽฏ

B+ Tree์˜ ํ•ต์‹ฌ ํŠน์„ฑ์„ ์ •๋ฆฌํ•˜๋ฉด:

ํŠน์„ฑ๋‚ด์šฉ
๊ท ํ˜• ํŠธ๋ฆฌ๋ชจ๋“  ๋ฆฌํ”„ ๋…ธ๋“œ๊นŒ์ง€์˜ ๊นŠ์ด๊ฐ€ ๋™์ผ โ†’ ํƒ์ƒ‰ ์‹œ๊ฐ„์ด ํ•ญ์ƒ O(log N)
์ •๋ ฌ๋œ ์ƒํƒœ ์œ ์ง€์‚ฝ์ž…/์‚ญ์ œ ์‹œ ์ž๋™์œผ๋กœ ์žฌ์ •๋ ฌ โ†’ ํ•ญ์ƒ ์ •๋ ฌ๋œ ์ƒํƒœ
๋ฆฌํ”„ ๋…ธ๋“œ ์—ฐ๊ฒฐ๋ฒ”์œ„ ๊ฒ€์ƒ‰ ์‹œ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ ์ˆœํšŒ๋กœ ๋น ๋ฅธ ์ฒ˜๋ฆฌ
ํŽ˜์ด์ง€ ๋‹จ์œ„ ์ €์žฅInnoDB ๊ธฐ๋ณธ ํŽ˜์ด์ง€ ํฌ๊ธฐ 16KB โ†’ ๋””์Šคํฌ I/O ์ตœ์†Œํ™”

๐Ÿ—๏ธ ํด๋Ÿฌ์Šคํ„ฐ๋“œ ์ธ๋ฑ์Šค vs ์„ธ์ปจ๋”๋ฆฌ ์ธ๋ฑ์Šค

InnoDB์—์„œ ์ธ๋ฑ์Šค๋ฅผ ์ดํ•ดํ•˜๋ ค๋ฉด ์ด ๋‘ ๊ฐ€์ง€ ๊ฐœ๋…์ด ํ•ต์‹ฌ์ด์•ผ!

๐Ÿ  ํด๋Ÿฌ์Šคํ„ฐ๋“œ ์ธ๋ฑ์Šค (Clustered Index)

ํ…Œ์ด๋ธ”๋‹น ๋”ฑ ํ•˜๋‚˜๋งŒ ์กด์žฌํ•ด.
PRIMARY KEY๊ฐ€ ๋ฐ”๋กœ ํด๋Ÿฌ์Šคํ„ฐ๋“œ ์ธ๋ฑ์Šค์•ผ.
์‹ค์ œ ๋ฐ์ดํ„ฐ ํ–‰์ด ์ด ์ธ๋ฑ์Šค ์ˆœ์„œ๋Œ€๋กœ ๋ฌผ๋ฆฌ์ ์œผ๋กœ ์ €์žฅ๋ผ.

โ†’ PK๋กœ ์กฐํšŒํ•˜๋ฉด ์ดˆ๊ณ ์†์ธ ์ด์œ ๊ฐ€ ๋ฐ”๋กœ ์ด๊ฒƒ!
๐Ÿ”— ์„ธ์ปจ๋”๋ฆฌ ์ธ๋ฑ์Šค (Secondary Index)

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;                     -- โŒ ์‚ฌ์šฉ ๋ถˆ๊ฐ€
๐Ÿ’ก ์™ผ์ชฝ ์ ‘๋‘์‚ฌ ๊ทœ์น™ (Leftmost Prefix Rule)
๋ณตํ•ฉ ์ธ๋ฑ์Šค๋Š” ์™ผ์ชฝ๋ถ€ํ„ฐ ์ˆœ์„œ๋Œ€๋กœ ์‚ฌ์šฉํ•ด์•ผ ํ•ด!
(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_typeSELECT ์œ ํ˜•SIMPLE, SUBQUERY, DERIVED ๋“ฑ
table์ ‘๊ทผํ•˜๋Š” ํ…Œ์ด๋ธ”-
typeโญ ์ ‘๊ทผ ๋ฐฉ์‹ (๊ฐ€์žฅ ์ค‘์š”!)์•„๋ž˜ ์ƒ์„ธ ์„ค๋ช… ์ฐธ๊ณ 
possible_keys์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ธ๋ฑ์Šค ๋ชฉ๋ก-
key์‹ค์ œ ์‚ฌ์šฉ๋œ ์ธ๋ฑ์ŠคNULL์ด๋ฉด ์ธ๋ฑ์Šค ๋ฏธ์‚ฌ์šฉ
key_len์‚ฌ์šฉ๋œ ์ธ๋ฑ์Šค ๊ธธ์ด(๋ฐ”์ดํŠธ)๋ณตํ•ฉ ์ธ๋ฑ์Šค ํ™œ์šฉ๋„ ํŒŒ์•…
rows์˜ˆ์ƒ ๊ฒ€์‚ฌ ํ–‰ ์ˆ˜์ž‘์„์ˆ˜๋ก ์ข‹์Œ
filtered์กฐ๊ฑด์— ๋งž๋Š” ํ–‰ ๋น„์œจ(%)๋†’์„์ˆ˜๋ก ์ข‹์Œ
Extra์ถ”๊ฐ€ ์ •๋ณดUsing index, Using filesort ๋“ฑ

๐Ÿšฆ type ์ปฌ๋Ÿผ โ€” ์„ฑ๋Šฅ์˜ ์‹ ํ˜ธ๋“ฑ

EXPLAIN์—์„œ type ์ปฌ๋Ÿผ์ด ๊ฐ€์žฅ ์ค‘์š”ํ•ด. ์„ฑ๋Šฅ ์ˆœ์„œ๋Œ€๋กœ ๋‚˜์—ดํ•˜๋ฉด:

EXPLAIN type ์„ฑ๋Šฅ ๋“ฑ๊ธ‰ํ‘œ โ† ์ตœ๊ณ  ์„ฑ๋Šฅ ์ตœ์•… ์„ฑ๋Šฅ โ†’ system ํ–‰์ด 1๊ฐœ์ธ ์‹œ์Šคํ…œ ํ…Œ์ด๋ธ” const PK/Unique๋กœ 1ํ–‰ ์กฐํšŒ eq_ref JOIN์—์„œ Unique ์ธ๋ฑ์Šค ref ๋น„์œ ๋‹ˆํฌ ์ธ๋ฑ์Šค ์‚ฌ์šฉ range ๋ฒ”์œ„ ์กฐ๊ฑด ์ธ๋ฑ์Šค ํƒ์ƒ‰ index ์ธ๋ฑ์Šค ํ’€ ์Šค์บ” ALL ํ’€ ํ…Œ์ด๋ธ” ์Šค์บ” ๐Ÿ˜ฑ โœ… ์ด ๋ฒ”์œ„๋Š” OK! const, eq_ref, ref, range ์ •๋„๋ฉด ์ธ๋ฑ์Šค๋ฅผ ์ž˜ ํ™œ์šฉํ•˜๊ณ  ์žˆ๋Š” ๊ฑฐ์•ผ. range๋Š” ์ƒํ™ฉ์— ๋”ฐ๋ผ ๊ฐœ์„  ์—ฌ์ง€ ์žˆ์Œ. โš ๏ธ ์ด ๋ฒ”์œ„๋Š” ๊ฐœ์„  ํ•„์š”! index๋Š” ์ธ๋ฑ์Šค ์ „์ฒด๋ฅผ ์Šค์บ”ํ•˜๋Š” ๊ฑฐ๋ผ ์ƒ๊ฐ๋ณด๋‹ค ๋А๋ฆด ์ˆ˜ ์žˆ์–ด. ALL์€ ๋ฌด์กฐ๊ฑด ๊ฐœ์„  ๋Œ€์ƒ! ๐Ÿšจ Extra ์ปฌ๋Ÿผ ์ฃผ์š” ๊ฐ’ ํ•ด์„ Using index ์ปค๋ฒ„๋ง ์ธ๋ฑ์Šค โœ… Using filesort ์ •๋ ฌ์— ์ธ๋ฑ์Šค ๋ฏธ์‚ฌ์šฉ โš ๏ธ Using temporary ์ž„์‹œ ํ…Œ์ด๋ธ” ์‚ฌ์šฉ โš ๏ธ Using where WHERE ํ•„ํ„ฐ๋ง ์ ์šฉ Using index condition ICP ์ ์šฉ โœ…

๐Ÿงช ์‹ค์ „ 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
๐Ÿ’ก pt-query-digest ์ถ”์ฒœ!
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์นด๋””๋„๋ฆฌํ‹ฐ ๋†’์€ ์ปฌ๋Ÿผ ๋จผ์ €์„ ํƒ๋„๊ฐ€ ๋†’์€ ์ปฌ๋Ÿผ์„ ์•ž์— ๋ฐฐ์น˜ํ•ด ๋น ๋ฅธ ํ•„ํ„ฐ๋ง
3ORDER BY ์ปฌ๋Ÿผ ํฌํ•จ์ •๋ ฌ ์ปฌ๋Ÿผ์„ ์ธ๋ฑ์Šค์— ํฌํ•จํ•˜๋ฉด filesort ์ œ๊ฑฐ
4SELECT ์ปฌ๋Ÿผ ํฌํ•จ (์ปค๋ฒ„๋ง)์ž์ฃผ ์กฐํšŒํ•˜๋Š” ์ปฌ๋Ÿผ์„ ์ธ๋ฑ์Šค์— ํฌํ•จํ•ด ์ปค๋ฒ„๋ง ์ธ๋ฑ์Šค ๊ตฌ์„ฑ
-- ์‹ค์ „ ์˜ˆ์‹œ: ์ด์ปค๋จธ์Šค ์ฃผ๋ฌธ ์กฐํšŒ
-- ์ฟผ๋ฆฌ ํŒจํ„ด:
-- 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)

ICP๋ž€?

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์œผ๋กœ ๊ฐ•์ œ ์ง€์ •๋„ ๊ฐ€๋Šฅ
๐Ÿ’ก 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 ๊ด€๋ จ ์žฌ๋Šฅ์„ ๊ฑฐ๋ž˜ํ•˜๋Š” ๋ถ„๋“ค๋„ ์ด ๊ธฐ์ค€์œผ๋กœ ์ฝ”๋“œ ๋ฆฌ๋ทฐ๋ฅผ ํ•˜๋ฉด ์ข‹์„ ๊ฑฐ์•ผ. ๐Ÿ˜Š

์ธ๋ฑ์Šค ์„ค๊ณ„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ โœ… ๐Ÿ“‹ ์„ค๊ณ„ ์ „ ํ™•์ธ์‚ฌํ•ญ ์ž์ฃผ ์‹คํ–‰๋˜๋Š” ์ฟผ๋ฆฌ ํŒจํ„ด ํŒŒ์•… WHERE ์ ˆ ์กฐ๊ฑด ์ปฌ๋Ÿผ ๋ชฉ๋ก ์ •๋ฆฌ JOIN ์กฐ๊ฑด ์ปฌ๋Ÿผ ํ™•์ธ ORDER BY / GROUP BY ์ปฌ๋Ÿผ ํ™•์ธ ๊ฐ ์ปฌ๋Ÿผ์˜ ์นด๋””๋„๋ฆฌํ‹ฐ ์ธก์ • ์ฝ๊ธฐ vs ์“ฐ๊ธฐ ๋น„์œจ ํŒŒ์•… ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ํฌ๊ธฐ ๋ฐ ์ฆ๊ฐ€์œจ ์˜ˆ์ธก ๐Ÿšซ ํ”ผํ•ด์•ผ ํ•  ํŒจํ„ด โœ— ์ปฌ๋Ÿผ์— ํ•จ์ˆ˜/์—ฐ์‚ฐ ์ ์šฉ ํ›„ ๋น„๊ต โœ— ์•ž์ชฝ ์™€์ผ๋“œ์นด๋“œ LIKE '%๊ฒ€์ƒ‰์–ด' โœ— ์•”๋ฌต์  ํƒ€์ž… ๋ณ€ํ™˜ โœ— ๋ถˆํ•„์š”ํ•œ ์ธ๋ฑ์Šค ๋‚จ๋ฐœ โœ— ์นด๋””๋„๋ฆฌํ‹ฐ ๋‚ฎ์€ ์ปฌ๋Ÿผ ๋‹จ๋… ์ธ๋ฑ์Šค โœ— UUID๋ฅผ PK๋กœ ์‚ฌ์šฉ (์„ฑ๋Šฅ ์ €ํ•˜) โœ… ๊ถŒ์žฅ ํŒจํ„ด EXPLAIN์œผ๋กœ ์‹คํ–‰๊ณ„ํš ๋ฐ˜๋“œ์‹œ ํ™•์ธ ๋ณตํ•ฉ ์ธ๋ฑ์Šค ์ปฌ๋Ÿผ ์ˆœ์„œ ์ตœ์ ํ™” ์ปค๋ฒ„๋ง ์ธ๋ฑ์Šค ์ ๊ทน ํ™œ์šฉ ์Šฌ๋กœ์šฐ ์ฟผ๋ฆฌ ๋กœ๊ทธ ์ •๊ธฐ์  ๋ถ„์„ ANALYZE TABLE ์ •๊ธฐ ์‹คํ–‰ ๋ฏธ์‚ฌ์šฉ ์ธ๋ฑ์Šค ์ฃผ๊ธฐ์  ์ •๋ฆฌ INT/BIGINT PK ์‚ฌ์šฉ ๊ถŒ์žฅ ๐Ÿ“ˆ ์„ฑ๋Šฅ ๋ชฉํ‘œ ๊ธฐ์ค€ EXPLAIN type: const/eq_ref/ref/range ๋ชฉํ‘œ Extra: Using index (์ปค๋ฒ„๋ง) ๋‹ฌ์„ฑ ๋ชฉํ‘œ Extra: Using filesort/temporary ์ œ๊ฑฐ ๋ชฉํ‘œ rows ์ˆ˜: ์ „์ฒด ํ–‰์˜ 10% ์ดํ•˜ ๋ชฉํ‘œ

๐Ÿ’ก 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์˜ ํ™œ์šฉ ๊ฐ€์น˜
์ธ๋ฑ์Šค ์‚ญ์ œ๋Š” ๋˜๋Œ๋ฆฌ๊ธฐ ์–ด๋ ต๊ณ  ์žฌ์ƒ์„ฑ์— ์‹œ๊ฐ„์ด ๋งŽ์ด ๊ฑธ๋ ค.
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
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.