[Daily morning study] PostgreSQL vs MySQL ๋น„๊ต

#daily morning study

Image


PostgreSQL vs MySQL ๋น„๊ต

๊ฐœ์š”

PostgreSQL๊ณผ MySQL์€ ๋‘˜ ๋‹ค ์˜คํ”ˆ์†Œ์Šค ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์ด์ง€๋งŒ, ์„ค๊ณ„ ์ฒ ํ•™๊ณผ ์ง€์› ๊ธฐ๋Šฅ์—์„œ ์ฐจ์ด๊ฐ€ ๊ฝค ํฌ๋‹ค.

  • MySQL: ์†๋„์™€ ๋‹จ์ˆœํ•จ์„ ์šฐ์„ ์‹œํ•ด์„œ ์ฝ๊ธฐ ์ค‘์‹ฌ ์›Œํฌ๋กœ๋“œ์— ๊ฐ•ํ•˜๋‹ค.
  • PostgreSQL: ํ‘œ์ค€ ์ค€์ˆ˜์™€ ํ™•์žฅ์„ฑ์„ ์šฐ์„ ์‹œํ•ด์„œ ๋ณต์žกํ•œ ์ฟผ๋ฆฌ์™€ ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ํƒ€์ž…์— ๊ฐ•ํ•˜๋‹ค.

ํ•ต์‹ฌ ์ฐจ์ด์  ์š”์•ฝ

ํ•ญ๋ชฉPostgreSQLMySQL
๋ผ์ด์„ ์ŠคPostgreSQL License (์ž์œ ๋„ ๋†’์Œ)GPL v2 (Oracle ์†Œ์œ )
SQL ํ‘œ์ค€ ์ค€์ˆ˜๋งค์šฐ ๋†’์Œ์ค‘๊ฐ„ (์ผ๋ถ€ ๋น„ํ‘œ์ค€ ๋ฌธ๋ฒ• ์‚ฌ์šฉ)
๊ธฐ๋ณธ ์Šคํ† ๋ฆฌ์ง€ ์—”์ง„๋‹จ์ผ (Heap)๋‹ค์ค‘ (InnoDB, MyISAM ๋“ฑ)
MVCC ๊ตฌํ˜„์ž์ฒด MVCCInnoDB MVCC
JSON ์ง€์›JSONB (๋ฐ”์ด๋„ˆ๋ฆฌ JSON, ์ธ๋ฑ์Šค ์ง€์›)JSON (ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜)
๋ฐฐ์—ด/๋ฒ”์œ„ ํƒ€์ž…์ง€์›๋ฏธ์ง€์›
ํ’€ํ…์ŠคํŠธ ๊ฒ€์ƒ‰์ง€์›์ง€์› (๋‹จ, PostgreSQL์ด ๋” ๊ฐ•๋ ฅ)
ํŒŒํ‹ฐ์…”๋‹์„ ์–ธ์  ํŒŒํ‹ฐ์…”๋‹ (v10+)์ง€์›
๋ณต์ œ์ŠคํŠธ๋ฆฌ๋ฐ ๋ณต์ œ, ๋…ผ๋ฆฌ์  ๋ณต์ œ๋ฐ”์ด๋„ˆ๋ฆฌ ๋กœ๊ทธ ๊ธฐ๋ฐ˜ ๋ณต์ œ
์œˆ๋„์šฐ ํ•จ์ˆ˜์™„์ „ ์ง€์›์ง€์› (v8+)
CTE (WITH ์ ˆ)์™„์ „ ์ง€์›์ง€์› (v8+)
์ปค๋ฎค๋‹ˆํ‹ฐ/ํด๋ผ์šฐ๋“œ ์ง€์›RDS, Cloud SQL, Azure ๋“ฑRDS, Cloud SQL, Azure ๋“ฑ

์•„ํ‚คํ…์ฒ˜ ์ฐจ์ด

ํ”„๋กœ์„ธ์Šค ๋ชจ๋ธ

  • PostgreSQL: ์—ฐ๊ฒฐ๋งˆ๋‹ค ๋ณ„๋„์˜ OS ํ”„๋กœ์„ธ์Šค๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. ๋ฉ”๋ชจ๋ฆฌ ๊ฒฉ๋ฆฌ๊ฐ€ ํ™•์‹คํ•˜์ง€๋งŒ, ์—ฐ๊ฒฐ์ด ๋งŽ์•„์ง€๋ฉด ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ์ปค์ง„๋‹ค. ์ด ๋•Œ๋ฌธ์— PgBouncer ๊ฐ™์€ ์ปค๋„ฅ์…˜ ํ’€๋Ÿฌ๋ฅผ ํ•จ๊ป˜ ์“ฐ๋Š” ๊ฒŒ ์ผ๋ฐ˜์ ์ด๋‹ค.
  • MySQL: ์Šค๋ ˆ๋“œ ๊ธฐ๋ฐ˜ ๋ชจ๋ธ์ด๋‹ค. ์—ฐ๊ฒฐ๋‹น ์Šค๋ ˆ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ํ”„๋กœ์„ธ์Šค ์ƒ์„ฑ ๋น„์šฉ์ด ์—†์–ด ๋Œ€๋Ÿ‰ ์—ฐ๊ฒฐ์— ์ƒ๋Œ€์ ์œผ๋กœ ์œ ๋ฆฌํ•˜๋‹ค.

์Šคํ† ๋ฆฌ์ง€ ์—”์ง„

MySQL์€ ์Šคํ† ๋ฆฌ์ง€ ์—”์ง„์„ ๊ต์ฒดํ•  ์ˆ˜ ์žˆ๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๊ฐ–๊ณ  ์žˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ InnoDB์ธ๋ฐ, ์˜ˆ์ „์—๋Š” ํŠธ๋žœ์žญ์…˜์ด ์•ˆ ๋˜๋Š” MyISAM์ด ๊ธฐ๋ณธ์ด์—ˆ๋‹ค. PostgreSQL์€ ๋‹จ์ผ ์Šคํ† ๋ฆฌ์ง€ ์—”์ง„์„ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹  ๋‚ด๋ถ€ ๊ตฌ์กฐ๋ฅผ ๋” ๊นŠ์ด ์ตœ์ ํ™”ํ–ˆ๋‹ค.


MVCC ๊ตฌํ˜„ ๋ฐฉ์‹

MVCC(Multi-Version Concurrency Control)๋Š” ์ฝ๊ธฐ์™€ ์“ฐ๊ธฐ๊ฐ€ ์„œ๋กœ ๋ธ”๋กœํ‚นํ•˜์ง€ ์•Š๋„๋ก ํ•˜๋Š” ํ•ต์‹ฌ ๊ธฐ๋ฒ•์ด๋‹ค.

PostgreSQL์˜ MVCC

  • ๋ ˆ์ฝ”๋“œ์— xmin, xmax ๊ฐ™์€ ํŠธ๋žœ์žญ์…˜ ID๋ฅผ ์ง์ ‘ ์ €์žฅํ•œ๋‹ค.
  • ๊ตฌ๋ฒ„์ „ ๋ ˆ์ฝ”๋“œ๋ฅผ ๊ฐ™์€ ํ…Œ์ด๋ธ”์— ๋‚จ๊ฒจ๋‘๋‹ค๊ฐ€ VACUUM ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ฃผ๊ธฐ์ ์œผ๋กœ ์ •๋ฆฌํ•œ๋‹ค.
  • ์“ฐ๊ธฐ๊ฐ€ ๋งŽ์œผ๋ฉด dead tuple์ด ์Œ“์—ฌ VACUUM ํŠœ๋‹์ด ์ค‘์š”ํ•ด์ง„๋‹ค.

MySQL InnoDB์˜ MVCC

  • ์–ธ๋‘ ๋กœ๊ทธ(undo log)๋ฅผ ๋ณ„๋„ ๊ณต๊ฐ„์— ์œ ์ง€ํ•œ๋‹ค.
  • ๊ตฌ๋ฒ„์ „ ๋ฐ์ดํ„ฐ๋ฅผ ํ…Œ์ด๋ธ”์ด ์•„๋‹Œ undo tablespace์— ๋ณด๊ด€ํ•˜๋ฏ€๋กœ ํ…Œ์ด๋ธ” bloat ๋ฌธ์ œ๊ฐ€ ์—†๋‹ค.
  • ๋Œ€์‹  ์–ธ๋‘ ๋กœ๊ทธ๊ฐ€ ๊ธธ์–ด์ง€๋ฉด ๋กค๋ฐฑ ๋น„์šฉ์ด ์ปค์ง„๋‹ค.

๋ฐ์ดํ„ฐ ํƒ€์ž… ๋น„๊ต

PostgreSQL์ด ์ง€์›ํ•˜๋Š” ํƒ€์ž…์ด ํ›จ์”ฌ ๋‹ค์–‘ํ•˜๋‹ค.

PostgreSQL ๊ณ ์œ  ํƒ€์ž…

-- ๋ฐฐ์—ด ํƒ€์ž…
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    tags TEXT[]
);
INSERT INTO orders (tags) VALUES (ARRAY['urgent', 'bulk']);

-- ๋ฒ”์œ„ ํƒ€์ž…
CREATE TABLE reservations (
    id SERIAL PRIMARY KEY,
    during TSRANGE
);
INSERT INTO reservations (during) VALUES ('[2026-05-17, 2026-05-20)');

-- JSONB (๋ฐ”์ด๋„ˆ๋ฆฌ JSON, GIN ์ธ๋ฑ์Šค ์ง€์›)
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    attributes JSONB
);
CREATE INDEX idx_attributes ON products USING GIN (attributes);
SELECT * FROM products WHERE attributes @> '{"color": "red"}';

MySQL์˜ JSON

-- JSON ํƒ€์ž… (ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜)
CREATE TABLE products (
    id INT AUTO_INCREMENT PRIMARY KEY,
    attributes JSON
);
-- ํ•จ์ˆ˜ํ˜• ์ธ๋ฑ์Šค๋กœ ํŠน์ • ๊ฒฝ๋กœ์— ์ธ๋ฑ์Šค ์ƒ์„ฑ ๊ฐ€๋Šฅ (v5.7.8+)
ALTER TABLE products ADD INDEX idx_color ((CAST(attributes->>'$.color' AS CHAR(50))));

PostgreSQL์˜ JSONB๋Š” ์ €์žฅ ์‹œ ํŒŒ์‹ฑํ•ด์„œ ๋ฐ”์ด๋„ˆ๋ฆฌ๋กœ ๋ณด๊ด€ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฝ๊ธฐ ์†๋„๊ฐ€ ๋น ๋ฅด๊ณ  GIN ์ธ๋ฑ์Šค๋ฅผ ํ†ตํ•ด JSON ๋‚ด๋ถ€ ๊ฐ’์œผ๋กœ ํšจ์œจ์ ์ธ ๊ฒ€์ƒ‰์ด ๋œ๋‹ค.


ํŠธ๋žœ์žญ์…˜๊ณผ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€

๋‘˜ ๋‹ค ACID๋ฅผ ๋ณด์žฅํ•˜์ง€๋งŒ ์ง€์›ํ•˜๋Š” ๊ฒฉ๋ฆฌ ์ˆ˜์ค€์— ์ฐจ์ด๊ฐ€ ์žˆ๋‹ค.

๊ฒฉ๋ฆฌ ์ˆ˜์ค€PostgreSQLMySQL InnoDB
READ UNCOMMITTED์ง€์› (์‹ค์ œ๋กœ๋Š” RC์ฒ˜๋Ÿผ ๋™์ž‘)์ง€์›
READ COMMITTED์ง€์›์ง€์›
REPEATABLE READ์ง€์›์ง€์› (๊ธฐ๋ณธ๊ฐ’)
SERIALIZABLE์ง€์›์ง€์›

MySQL InnoDB์˜ ๊ธฐ๋ณธ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€์€ REPEATABLE READ์ด๊ณ , PostgreSQL์˜ ๊ธฐ๋ณธ๊ฐ’์€ READ COMMITTED๋‹ค.

PostgreSQL์˜ SERIALIZABLE์€ SSI(Serializable Snapshot Isolation)๋ฅผ ๊ตฌํ˜„ํ•ด์„œ ์ง„์งœ ์ง๋ ฌํ™” ๊ฐ€๋Šฅ์„ฑ์„ ๋ณด์žฅํ•œ๋‹ค. MySQL์˜ SERIALIZABLE์€ SELECT์— ๊ณต์œ  ๋ฝ์„ ๊ฑธ์–ด ๊ตฌํ˜„ํ•˜๋ฏ€๋กœ ์„ฑ๋Šฅ ๋น„์šฉ์ด ๋” ํฌ๋‹ค.


๋ณต์ œ(Replication)

PostgreSQL

  • ์ŠคํŠธ๋ฆฌ๋ฐ ๋ณต์ œ: WAL(Write-Ahead Log)์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์Šคํƒ ๋ฐ”์ด์— ์ „์†กํ•œ๋‹ค. ๋ฌผ๋ฆฌ์  ๋ณต์ œ๋ผ์„œ ๋ฐ”์ดํŠธ ๋‹จ์œ„๋กœ ๋™์ผํ•œ ๋ณต์‚ฌ๋ณธ์„ ๋งŒ๋“ ๋‹ค.
  • ๋…ผ๋ฆฌ์  ๋ณต์ œ: ํ…Œ์ด๋ธ” ๋‹จ์œ„๋กœ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ฐœํ–‰/๊ตฌ๋…ํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค. ๋‹ค๋ฅธ ๋ฒ„์ „ ๊ฐ„ ๋ณต์ œ๋‚˜ ์„ ํƒ์  ๋ณต์ œ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • Patroni, repmgr ๊ฐ™์€ ์™ธ๋ถ€ ๋„๊ตฌ๋กœ ์ž๋™ ํŽ˜์ผ์˜ค๋ฒ„๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๊ฒŒ ์ผ๋ฐ˜์ ์ด๋‹ค.

MySQL

  • ๋ฐ”์ด๋„ˆ๋ฆฌ ๋กœ๊ทธ(binlog) ๊ธฐ๋ฐ˜ ๋ณต์ œ: Statement ๊ธฐ๋ฐ˜, Row ๊ธฐ๋ฐ˜, Mixed ๋ฐฉ์‹์ด ์žˆ๋‹ค. Row ๊ธฐ๋ฐ˜์ด ๊ฐ€์žฅ ์•ˆ์ •์ ์ด๋‹ค.
  • GTID(Global Transaction ID): ํŠธ๋žœ์žญ์…˜๋งˆ๋‹ค ๊ณ ์œ  ID๋ฅผ ๋ถ€์—ฌํ•ด์„œ ๋ณต์ œ ์œ„์น˜๋ฅผ ์ถ”์ ํ•œ๋‹ค. ํŽ˜์ผ์˜ค๋ฒ„ ์‹œ ๋ณต์ œ ์žฌ๊ตฌ์„ฑ์ด ์‰ฝ๋‹ค.
  • MySQL Router, Orchestrator ๋“ฑ์œผ๋กœ HA๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค.

์„ฑ๋Šฅ ํŠน์„ฑ

์ฝ๊ธฐ ์„ฑ๋Šฅ

๋‹จ์ˆœํ•œ OLTP ์ฝ๊ธฐ(PK ์กฐํšŒ, ๋‹จ์ˆœ JOIN)์—์„œ๋Š” MySQL์ด ์กฐ๊ธˆ ๋” ๋น ๋ฅธ ๊ฒฝํ–ฅ์ด ์žˆ๋‹ค. ์Šค๋ ˆ๋“œ ๋ชจ๋ธ๊ณผ ์ฟผ๋ฆฌ ์บ์‹œ(deprecated๋์ง€๋งŒ) ๋•๋ถ„์— ๋‹จ์ˆœ ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ๋Ÿ‰์ด ๋†’๋‹ค.

๋ณต์žกํ•œ ๋ถ„์„ ์ฟผ๋ฆฌ(์œˆ๋„์šฐ ํ•จ์ˆ˜, ๋ณต์žกํ•œ ์ง‘๊ณ„, ์„œ๋ธŒ์ฟผ๋ฆฌ)์—์„œ๋Š” PostgreSQL์˜ ํ”Œ๋ž˜๋„ˆ๊ฐ€ ๋” ์ •๊ตํ•œ ์‹คํ–‰ ๊ณ„ํš์„ ์„ธ์šด๋‹ค.

์“ฐ๊ธฐ ์„ฑ๋Šฅ

PostgreSQL์€ dead tuple ๋•Œ๋ฌธ์— ์“ฐ๊ธฐ๊ฐ€ ๋งŽ์€ ํ…Œ์ด๋ธ”์—์„œ VACUUM ๋ถ€ํ•˜๊ฐ€ ์ƒ๊ธด๋‹ค. autovacuum ์„ค์ •์„ ์ž˜ ํŠœ๋‹ํ•ด์•ผ ํ•œ๋‹ค.

MySQL InnoDB๋Š” ์–ธ๋‘ ๋กœ๊ทธ ๋ฐฉ์‹์ด๋ผ ํ…Œ์ด๋ธ” bloat์€ ์—†์ง€๋งŒ, ์–ธ๋‘ ๋กœ๊ทธ๊ฐ€ ๊ธธ์–ด์ง€๋Š” ๊ธด ํŠธ๋žœ์žญ์…˜์—๋Š” ์ทจ์•ฝํ•˜๋‹ค.


์–ธ์ œ ๋ฌด์—‡์„ ์„ ํƒํ• ๊นŒ

PostgreSQL์„ ์„ ํƒํ•˜๋Š” ๊ฒฝ์šฐ

  • ๋ณต์žกํ•œ ์ฟผ๋ฆฌ, ์œˆ๋„์šฐ ํ•จ์ˆ˜, CTE๋ฅผ ๋งŽ์ด ์“ธ ๋•Œ
  • JSON ๋ฐ์ดํ„ฐ๋ฅผ ์กฐ๊ฑด ๊ฒ€์ƒ‰ํ•˜๊ฑฐ๋‚˜ ์ธ๋ฑ์‹ฑํ•ด์•ผ ํ•  ๋•Œ
  • ์ง€๋ฆฌ ๋ฐ์ดํ„ฐ(PostGIS ํ™•์žฅ)๋ฅผ ๋‹ค๋ฃฐ ๋•Œ
  • SQL ํ‘œ์ค€์„ ์—„๊ฒฉํ•˜๊ฒŒ ๋”ฐ๋ฅด๋Š” ํ™˜๊ฒฝ
  • SERIALIZABLE ๊ฒฉ๋ฆฌ๊ฐ€ ์ง„์งœ๋กœ ํ•„์š”ํ•œ ๊ธˆ์œต, ๊ฒฐ์ œ ์‹œ์Šคํ…œ

MySQL์„ ์„ ํƒํ•˜๋Š” ๊ฒฝ์šฐ

  • ๋‹จ์ˆœ CRUD ์ค‘์‹ฌ์˜ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜
  • ์ฝ๊ธฐ ์ฒ˜๋ฆฌ๋Ÿ‰์ด ์••๋„์ ์œผ๋กœ ๋งŽ์€ ์„œ๋น„์Šค (SNS ํ”ผ๋“œ ๋“ฑ)
  • ๊ธฐ์กด ๋ ˆ๊ฑฐ์‹œ ์ฝ”๋“œ๋‚˜ ์ธํ”„๋ผ๊ฐ€ MySQL ๊ธฐ๋ฐ˜์ผ ๋•Œ
  • ๊ด€๋ฆฌ ์ธ๋ ฅ์ด MySQL์— ์ต์ˆ™ํ•œ ํ™˜๊ฒฝ

ํด๋ผ์šฐ๋“œ ๋งค๋‹ˆ์ง€๋“œ ์„œ๋น„์Šค

์„œ๋น„์ŠคPostgreSQLMySQL
AWSRDS for PostgreSQL, Aurora PostgreSQLRDS for MySQL, Aurora MySQL
GCPCloud SQL for PostgreSQL, AlloyDBCloud SQL for MySQL
AzureAzure Database for PostgreSQLAzure Database for MySQL

Aurora PostgreSQL๊ณผ Aurora MySQL์€ AWS๊ฐ€ ํด๋ผ์šฐ๋“œ์— ๋งž๊ฒŒ ์žฌ์„ค๊ณ„ํ•œ ์—”์ง„์ด๋ผ ๊ธฐ์กด ํ˜ธํ™˜์„ฑ์„ ์œ ์ง€ํ•˜๋ฉด์„œ ์„ฑ๋Šฅ์„ ๋†’์ธ ๋ฒ„์ „์ด๋‹ค.


์ •๋ฆฌ

PostgreSQL์€ ๊ธฐ๋Šฅ์ด ํ’๋ถ€ํ•˜๊ณ  SQL ํ‘œ์ค€์„ ์ž˜ ๋”ฐ๋ฅด๋ฉฐ, ๋ณต์žกํ•œ ์ฟผ๋ฆฌ์™€ ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ํƒ€์ž…์ด ํ•„์š”ํ•œ ์‹œ์Šคํ…œ์— ์ ํ•ฉํ•˜๋‹ค. MySQL์€ ๋‹จ์ˆœ OLTP ์›Œํฌ๋กœ๋“œ์™€ ๋Œ€๋Ÿ‰ ์ฝ๊ธฐ ์ฒ˜๋ฆฌ์—์„œ ๊ฐ€๋ณ๊ณ  ๋น ๋ฅด๋‹ค๋Š” ์žฅ์ ์ด ์žˆ๋‹ค. ์–ด๋–ค ๊ฒŒ ๋‚ซ๋‹ค๊ธฐ๋ณด๋‹ค๋Š” ์„œ๋น„์Šค์˜ ์ฟผ๋ฆฌ ํŒจํ„ด๊ณผ ํŒ€์˜ ๊ฒฝํ—˜์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ ํƒํ•˜๋Š” ๊ฒŒ ํ˜„์‹ค์ ์ด๋‹ค.