[Daily morning study] PostgreSQL vs MySQL ๋น๊ต
#daily morning study
์ ๋์ ๋น๊ตํ๋๊ฐ
์คํ์์ค RDBMS ์์ฅ์์ ๊ฐ์ฅ ๋ง์ด ์ฐ์ด๋ ๋ DB๊ฐ MySQL๊ณผ PostgreSQL์ด๋ค. MySQL์ ์น ์๋น์ค ์ด์ฐฝ๊ธฐ๋ถํฐ LAMP ์คํ์ ํ ์ถ์ ๋ด๋นํ๊ณ , PostgreSQL(์ดํ Postgres)์ โ๊ฐ์ฅ ๊ธฐ๋ฅ์ด ํ๋ถํ ์คํ์์ค DBโ๋ฅผ ๋ชฉํ๋ก ๋ฐ์ ํด์๋ค. ๋ฌด์์ ์ ํํ๋๋๋ ๋จ์ํ ์ทจํฅ ๋ฌธ์ ๊ฐ ์๋๋ผ ํธ๋์ญ์ ๋์์ฑ, ๋ฐ์ดํฐ ํ์ ์๊ตฌ์ฌํญ, ํ์ฅ์ฑ, ๋ผ์ด์ ์ค ๋ฑ ์ค์ง์ ์ธ ์ฐจ์ด์์ ๋น๋กฏ๋๋ค.
์ํคํ ์ฒ ์ฐจ์ด
MySQL์ ์คํ ๋ฆฌ์ง ์์ง ๊ตฌ์กฐ
MySQL์ ํ๋ฌ๊ทธ์ธ ๊ฐ๋ฅํ ์คํ ๋ฆฌ์ง ์์ง ๊ตฌ์กฐ๋ฅผ ๊ฐ๋๋ค. ์ฟผ๋ฆฌ ๋ ์ด์ด์ ์คํ ๋ฆฌ์ง ๋ ์ด์ด๊ฐ ๋ถ๋ฆฌ๋์ด ์์ด, ๊ฐ์ SQL๋ก InnoDB, MyISAM, Memory, CSV ๋ฑ ๋ค๋ฅธ ์์ง์ ์ฌ์ฉํ ์ ์๋ค.
- InnoDB โ ๊ธฐ๋ณธ ์์ง. ํธ๋์ญ์ , ์ธ๋ํค, Row-level locking ์ง์
- MyISAM โ ํธ๋์ญ์ ์์, Table-level locking. ์ฝ๊ธฐ ์ง์ฝ์ ๋ ๊ฑฐ์์ ์ฌ์ฉ
- ํ๋ MySQL์์๋ ๊ฑฐ์ InnoDB๋ง ์ด๋ค๊ณ ๋ด๋ ๋ฌด๋ฐฉํ๋ค
PostgreSQL์ ๋จ์ผ ์คํ ๋ฆฌ์ง ์์ง
Postgres๋ ์คํ ๋ฆฌ์ง ์์ง์ด ํ๋๋ค. ๋์ ํ ์ด๋ธ ์ก์ธ์ค ๋ฉ์๋(Table Access Method) API๋ฅผ ํตํด ์ธ๋ถ ์์ง์ ๋ถ์ผ ์ ์๋ ๊ตฌ์กฐ(pg17 ์ดํ ํ์ฅ ์์ )๊ฐ ์์ง๋ง ์ค์ฉ์ ์ผ๋ก๋ ๊ธฐ๋ณธ heap ์คํ ๋ฆฌ์ง๋ฅผ ์ฌ์ฉํ๋ค. ๋ณต์ก์ฑ ๋์ ์ฌ์ธต์ ์ธ ๊ธฐ๋ฅ ํตํฉ์ ์ง์คํ๋ค.
MVCC ๊ตฌํ ๋ฐฉ์
๋ DB ๋ชจ๋ MVCC(Multi-Version Concurrency Control)๋ฅผ ์ฌ์ฉํ์ง๋ง ๋ฐฉ์์ด ๋ค๋ฅด๋ค.
MySQL InnoDB์ MVCC
Undo ๋ก๊ทธ ๊ธฐ๋ฐ. ํ์ ์ ๋ฐ์ดํธํ๋ฉด ์ด์ ๋ฒ์ ์ undo ๋ก๊ทธ์ ๋ณด๊ดํ๊ณ ์๋ณธ ํ์ ์ ๊ฐ์ผ๋ก ๋ฎ์ด์ด๋ค. ์ค๋๋ ๋ฒ์ ์ด ํ์ํ ํธ๋์ญ์ ์ undo ๋ก๊ทธ๋ฅผ ์ญ์ถ์ ํ๋ค.
ํ ์
๋ฐ์ดํธ ์ : [ data=old | txn_id=100 | roll_pointer โ undo_log ]
ํ ์
๋ฐ์ดํธ ํ: [ data=new | txn_id=200 | roll_pointer โ old_version ]
โ
undo_log: old_version ๋ณด๊ด
์ฅ๊ธฐ ํธ๋์ญ์ ์ด ์์ผ๋ฉด undo ๋ก๊ทธ๊ฐ ์์ฌ undo tablespace ๋ถ๋ด์ด ์ปค์ง๋ค.
PostgreSQL์ MVCC
Postgres๋ ํ ์์ฒด์ ์ฌ๋ฌ ๋ฒ์ ์ ํ
์ด๋ธ ๋ด๋ถ์ ์ ์ฅํ๋ค (Heap tuple versioning). ์
๋ฐ์ดํธํ๋ฉด ์ ํํ์ ํ
์ด๋ธ์ INSERTํ๊ณ ์ด์ ํํ์ xmax๋ฅผ ์ค์ ํด ์ญ์ ํ์ํ๋ค.
์
๋ฐ์ดํธ ์ : [ xmin=100, xmax=0, data=old ] โ ์ด์์๋ ํํ
์
๋ฐ์ดํธ ํ: [ xmin=100, xmax=200, data=old ] โ ์ฃฝ์ ํํ (xmax ์ค์ ๋จ)
[ xmin=200, xmax=0, data=new ] โ ์ ํํ
์ฃฝ์ ํํ์ VACUUM ํ๋ก์ธ์ค๊ฐ ๋์ค์ ํ์ํ๋ค. VACUUM์ด ์ ๋ ๋์ง ์์ผ๋ฉด ํ ์ด๋ธ bloat์ด ์๊ธด๋ค.
| ํญ๋ชฉ | MySQL InnoDB | PostgreSQL |
|---|---|---|
| ๊ตฌ๋ฒ์ ๋ณด๊ด ์์น | Undo ๋ก๊ทธ | ํ ์ด๋ธ ๋ด๋ถ (dead tuple) |
| ์ ๋ฆฌ ๋ฉ์ปค๋์ฆ | Purge thread | VACUUM |
| ์ฅ๊ธฐ ํธ๋์ญ์ ์ํฅ | undo log ์ฆ๊ฐ | table bloat |
ํธ๋์ญ์ ๊ฒฉ๋ฆฌ ์์ค
| ๊ฒฉ๋ฆฌ ์์ค | MySQL InnoDB | PostgreSQL |
|---|---|---|
| READ UNCOMMITTED | ์ง์ | ์์ (READ COMMITTED๋ก ์ฒ๋ฆฌ) |
| READ COMMITTED | ์ง์ | ์ง์ |
| REPEATABLE READ | ๊ธฐ๋ณธ๊ฐ, Gap Lock์ผ๋ก Phantom Read ๋ฐฉ์ง | ์ง์ |
| SERIALIZABLE | ์ง์ | ์ง์ (SSI ๊ตฌํ) |
Postgres์ SSI(Serializable Snapshot Isolation)๋ ์ ๊ธ ์์ด ์ง๋ ฌํ ๊ฐ๋ฅ์ฑ์ ๋ณด์ฅํ๋ ๊ธฐ๋ฒ์ผ๋ก, ์ฝ๊ธฐ๊ฐ ๋ง์ OLTP์์ ์ฑ๋ฅ ์ด์ ์ด ์๋ค.
MySQL์ REPEATABLE READ์์ Gap Lock์ ํธ๋์ญ์ ๊ฐ ๊ต์ฐฉ(Deadlock)์ ์ ๋ฐํ๊ธฐ ์ฝ๋ค. ๊ณ ๋๋ก ๋์์ ์ธ ์ฐ๊ธฐ ์ํฌ๋ก๋์์ ์ ๊ธ ๊ฒฝํฉ ๋ฌธ์ ๊ฐ ์์ฃผ ๋ฐ์ํ๋ค.
๋ฐ์ดํฐ ํ์ ๊ณผ ํ์ฅ์ฑ
PostgreSQL์ด ์ ๊ณตํ๋ ๋ฐ์ดํฐ ํ์ ์ด ํจ์ฌ ํ๋ถํ๋ค.
| ํ์ | MySQL | PostgreSQL |
|---|---|---|
| JSON | JSON, JSON_TABLE (8.0+) | JSON, JSONB (์ธ๋ฑ์ฑ ๊ฐ๋ฅ) |
| ๋ฐฐ์ด | ์์ | integer[], text[] ๋ฑ ๊ธฐ๋ณธ ์ง์ |
| ๋ฒ์ | ์์ | int4range, tsrange ๋ฑ |
| UUID | VARCHAR๋ก ์ ์ฅ | ์ ์ฉ UUID ํ์ |
| ๊ธฐํ/๊ณต๊ฐ | ๊ธฐ๋ณธ Geometry | PostGIS ํ์ฅ์ผ๋ก ์ ๋ฐ GIS |
| ์ ๋ฌธ ๊ฒ์ | FULLTEXT ์ธ๋ฑ์ค | tsvector, tsquery, GIN ์ธ๋ฑ์ค |
| ์ฌ์ฉ์ ์ ์ ํ์ | ENUM, SET ์ ๋ | CREATE TYPE, ๋ณตํฉ ํ์
, ๋๋ฉ์ธ |
Postgres์์ JSONB๋ ๋ฐ์ด๋๋ฆฌ ํฌ๋งท์ผ๋ก ์ ์ฅ๋์ด ํ์ฑ ์์ด ์ธ๋ฑ์ฑํ ์ ์๋ค. MongoDB ๋์ PostgreSQL๋ก JSON ์ค์ฌ ์๋น์ค๋ฅผ ๊ตฌ์ถํ๋ ์ฌ๋ก๊ฐ ๋ง์ ์ด์ ๊ฐ ์ฌ๊ธฐ์ ์๋ค.
์ธ๋ฑ์ค ์ข ๋ฅ
PostgreSQL์ ์ธ๋ฑ์ค ์ ํ์ง๊ฐ ๋ค์ํ๋ค.
| ์ธ๋ฑ์ค ํ์ | MySQL | PostgreSQL |
|---|---|---|
| B-Tree | O | O |
| Hash | O (์ ํ์ ) | O |
| GiST | X | O (๊ธฐํ, ๋ฒ์, ์ ๋ฌธ๊ฒ์) |
| GIN | X | O (๋ฐฐ์ด, JSONB, ์ ๋ฌธ๊ฒ์) |
| BRIN | X | O (๋ฌผ๋ฆฌ์ ์ผ๋ก ์ ๋ ฌ๋ ๋์ฉ๋ ํ ์ด๋ธ) |
| ๋ถ๋ถ ์ธ๋ฑ์ค | X | O (WHERE ์กฐ๊ฑด๋ถ ์ธ๋ฑ์ค) |
| ํํ์ ์ธ๋ฑ์ค | ํจ์ ์ธ๋ฑ์ค(์ ํ์ ) | O (lower(email) ๋ฑ) |
๋ถ๋ถ ์ธ๋ฑ์ค(Partial Index) ์์:
-- ํ์ฑ ์ฌ์ฉ์์ ๋ํ ์ธ๋ฑ์ค๋ง ์์ฑ
CREATE INDEX idx_active_users ON users (email)
WHERE is_active = TRUE;
๋นํ์ฑ ์ฌ์ฉ์๊ฐ ๋ง๋ค๋ฉด ์ธ๋ฑ์ค ํฌ๊ธฐ๋ฅผ ํฌ๊ฒ ์ค์ผ ์ ์๋ค.
๋ณต์ ์ ๊ณ ๊ฐ์ฉ์ฑ
MySQL ๋ณต์
- Binary Log ๊ธฐ๋ฐ ๋ณต์ : Statement, Row, Mixed ์ธ ๊ฐ์ง ํฌ๋งท
- GTID(Global Transaction ID) ๋ณต์ : 5.6๋ถํฐ ๋์ , ํ์ผ์ค๋ฒ๊ฐ ์ฌ์์ง
- Group Replication / InnoDB Cluster: MySQL 8.0์ ๊ณต์ HA ์๋ฃจ์
- ProxySQL ๊ฐ์ ๋ฏธ๋ค์จ์ด๋ก ์ฝ๊ธฐ/์ฐ๊ธฐ ๋ถ๋ฆฌ๋ฅผ ๋ง์ด ๊ตฌ์ฑ
PostgreSQL ๋ณต์
- WAL ๊ธฐ๋ฐ Streaming Replication: ๊ธฐ๋ณธ HA ๋ฐฉ์.
primary_conninfo๋ก ์คํ ๋ฐ์ด ๊ตฌ์ฑ - Logical Replication: ํน์ ํ ์ด๋ธ/๋ ผ๋ฆฌ ๋ณ๊ฒฝ์ฌํญ๋ง ๋ณต์ . ์ด๊ธฐ์ข ๋ฒ์ ๊ฐ ๋ณต์ ๊ฐ๋ฅ
- Patroni: ์ ๊ณ ํ์ค HA ๋๊ตฌ. etcd/Consul/ZooKeeper๋ก ๋ฆฌ๋ ์ ์ถ ๊ด๋ฆฌ
# Patroni ๊ตฌ์ฑ ์์ (patroni.yml ์ผ๋ถ)
restapi:
listen: 0.0.0.0:8008
connect_address: 10.0.0.1:8008
postgresql:
listen: 0.0.0.0:5432
data_dir: /var/lib/postgresql/14/main
parameters:
max_connections: 200
wal_level: replica
์ฑ๋ฅ ํน์ฑ
์ฝ๊ธฐ ์ฑ๋ฅ
๋จ์ PK ์กฐํ๋ ์ธ๋ฑ์ค ๋ฒ์ ์ค์บ์ MySQL์ด ์ฝ๊ฐ ๋น ๋ฅธ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค. InnoDB์ Clustered Index ๊ตฌ์กฐ(PK๊ฐ ํ ์ด๋ธ ๋ฐ์ดํฐ์ ํจ๊ป ์ ์ฅ)๊ฐ PK ๊ธฐ๋ฐ ์กฐํ์ ์ต์ ํ๋์ด ์๊ธฐ ๋๋ฌธ์ด๋ค.
Postgres๋ ํ
์ด๋ธ๊ณผ ์ธ๋ฑ์ค๊ฐ ๋ถ๋ฆฌ๋ Heap ๊ตฌ์กฐ๋ค. PK๋ก ์กฐํํด๋ ์ธ๋ฑ์ค โ heap ํ
์ด๋ธ ์์ผ๋ก ๋ ๋ฒ ์ ๊ทผ(Index Scan + Heap Fetch)ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค. ๋ค๋ง Index Only Scan์ด๋ Covering Index๋ก ์ด๋ฅผ ํํผํ ์ ์๋ค.
์ฐ๊ธฐ ์ฑ๋ฅ
๋ณต์กํ ์ฟผ๋ฆฌ๋ JOIN์ด ๋ง์ OLAP์ฑ ์ฟผ๋ฆฌ์์๋ Postgres์ ์ฟผ๋ฆฌ ํ๋๋๊ฐ ๋ ์ ๊ตํ ์คํ ๊ณํ์ ๋ง๋ค์ด๋ด๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค. Hash Join, Merge Join, ๋ณ๋ ฌ ์ฟผ๋ฆฌ ์คํ์ด ๋ ํ๋ถํ๊ฒ ์ง์๋๋ค.
VACUUM ์ค๋ฒํค๋
Postgres์ VACUUM์ dead tuple์ ํ์ํ๋ ํ์ ์์ ์ด๋ค. Autovacuum ์ค์ ์ด ์๋ชป๋๋ฉด ํ ์ด๋ธ bloat์ด ๋ฐ์ํด ์ฑ๋ฅ์ด ์ ํ๋๋ค. write-heavy ํ๊ฒฝ์์๋ VACUUM ํ๋์ด ์ค์ํ ์ด์ ํฌ์ธํธ๋ค.
-- ํ
์ด๋ธ bloat ํ์ธ
SELECT relname, n_dead_tup, n_live_tup,
round(n_dead_tup::numeric / nullif(n_live_tup,0) * 100, 2) AS dead_ratio
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC;
์คํ ์ด๋ ํ๋ก์์ / ํจ์
Postgres๋ ์ฌ๋ฌ ์ธ์ด๋ก ํ๋ก์์ ๋ฅผ ์์ฑํ ์ ์๋ค.
| ํญ๋ชฉ | MySQL | PostgreSQL |
|---|---|---|
| ํ๋ก์์ ์ธ์ด | SQL, ์ ํ์ | PL/pgSQL, PL/Python, PL/Perl, PL/v8 ๋ฑ |
| ๋ฐํ ํ์ | ๋จ์ผ ๊ฒฐ๊ณผ์ | TABLE, SETOF, ๋ณตํฉ ํ์ ๋ฑ |
| ํธ๋์ญ์ ์ ์ด | ํ๋ก์์ ๋ด BEGIN/COMMIT (8.0+) | ๋์ผ |
PL/pgSQL ์์:
CREATE OR REPLACE FUNCTION get_user_rank(p_user_id BIGINT)
RETURNS INT AS $$
DECLARE
v_rank INT;
BEGIN
SELECT rank INTO v_rank
FROM (
SELECT user_id, RANK() OVER (ORDER BY score DESC) AS rank
FROM users
) ranked
WHERE user_id = p_user_id;
RETURN v_rank;
END;
$$ LANGUAGE plpgsql;
๋ผ์ด์ ์ค์ ์ํ๊ณ
| ํญ๋ชฉ | MySQL | PostgreSQL |
|---|---|---|
| ๋ผ์ด์ ์ค | GPL v2 (์คํ์์ค), ์์ฉ ๋ผ์ด์ ์ค ๋ณ๋ | PostgreSQL License (BSD ์ ์ฌ, ๋งค์ฐ ์์ ๋ก์) |
| ์์ | Oracle ์ธ์ (2010) | ์ปค๋ฎค๋ํฐ ์ฃผ๋ |
| ํด๋ผ์ฐ๋ ๊ด๋ฆฌํ | AWS RDS/Aurora, GCP Cloud SQL, Azure DB | RDS for PostgreSQL, Cloud SQL, Azure DB |
| ORM ์ง์ | ๋งค์ฐ ๊ด๋ฒ์ | ๊ด๋ฒ์ (์ ์ Postgres ์ฐ์ ์ถ์ธ) |
MySQL์ Oracle ์ธ์ ์ดํ ์ผ๋ถ ๊ฐ๋ฐ์๋ค์ด MariaDB๋ก ๋ถ๊ธฐํ๋ค. ๋ผ์ด์ ์ค ๋ฆฌ์คํฌ๋ฅผ ์ฐ๋ คํ๋ ๊ธฐ์ ์ Postgres๋ฅผ ์ ํธํ๋ ๊ฒฝํฅ์ด ์๋ค.
์ธ์ ๋ฌด์์ ์ ํํ ๊น
MySQL์ด ์ ๋ฆฌํ ๊ฒฝ์ฐ
- ๋จ์ CRUD ์ค์ฌ์ ์ฝ๊ธฐ ์ง์ฝ์ ์น ์๋น์ค
- MySQL ์ํ๊ณ์ ์ต์ํ ํ
- ๊ธฐ์กด MySQL ์ธํ๋ผ์์ ์ฐ๋
PostgreSQL์ด ์ ๋ฆฌํ ๊ฒฝ์ฐ
- ๋ณต์กํ ์ฟผ๋ฆฌ, ๋ถ์, ์ง๊ณ๊ฐ ๋ง์ ์๋น์ค
- JSONB, ๋ฐฐ์ด, ๋ฒ์ ํ์ ๋ฑ ๋ค์ํ ๋ฐ์ดํฐ ํ์ ์ด ํ์ํ ๊ฒฝ์ฐ
- GIS, ์ ๋ฌธ ๊ฒ์ ๋ฑ ํ์ฅ ๊ธฐ๋ฅ์ด ํ์ํ ๊ฒฝ์ฐ
- ์๊ฒฉํ ์ง๋ ฌํ ๊ฒฉ๋ฆฌ๊ฐ ํ์ํ ๊ธ์ต/ํํ ํฌ ๋๋ฉ์ธ
- ์ฅ๊ธฐ์ ์ผ๋ก ๋ผ์ด์ ์ค ์์ ๋๊ฐ ์ค์ํ ๊ฒฝ์ฐ
์ ๋ฆฌ
| ํญ๋ชฉ | MySQL | PostgreSQL |
|---|---|---|
| MVCC ๋ฐฉ์ | Undo ๋ก๊ทธ | Heap tuple versioning + VACUUM |
| ๊ธฐ๋ณธ ๊ฒฉ๋ฆฌ ์์ค | REPEATABLE READ | READ COMMITTED |
| ๋ฐ์ดํฐ ํ์ | ๋ณดํต | ๋งค์ฐ ํ๋ถ (JSONB, ๋ฐฐ์ด, GIS ๋ฑ) |
| ์ธ๋ฑ์ค ์ข ๋ฅ | B-Tree ์ค์ฌ | B-Tree, GIN, GiST, BRIN ๋ฑ |
| ๋จ์ ์ฝ๊ธฐ | ๋น ๋ฆ (Clustered PK) | ๋ณดํต (Heap ๋ถ๋ฆฌ) |
| ๋ณต์กํ ์ฟผ๋ฆฌ | ๋ณดํต | ์ฐ์ํ ์ฟผ๋ฆฌ ํ๋๋ |
| ํ์ฅ์ฑ | ์ ํ์ | ๋งค์ฐ ํ๋ถ (Extension ์์คํ ) |
| ๋ผ์ด์ ์ค | GPL / Oracle | BSD ์ ์ฌ, ๋งค์ฐ ์์ ๋ก์ |
๋ ๋ค ์ฑ์ํ ํ๋ก๋์ DB์ง๋ง, ๊ธฐ๋ฅ์ ๊น์ด์ ํ์ฅ์ฑ ์ธก๋ฉด์์ ํ๋ ์๋น์ค๋ ์ ์ PostgreSQL์ ์ ํํ๋ ์ถ์ธ๋ค. MySQL์ ๋จ์ํ ์น ์๋น์ค์์ ์ฌ์ ํ ์ถฉ๋ถํ ์ ํ์ง์ด๊ณ , ์ด์ ๋ ธํ์ฐ๊ฐ ํ๋ถํ ํ์ด๋ผ๋ฉด ์๋ ๋ฉด์์ ์ด์ ์ ๊ฐ์ ธ๊ฐ ์ ์๋ค.