Theme:

데이터를 실수로 삭제했을 때, 또는 서버를 하나 더 복제하고 싶을 때, MySQL은 어떤 기록을 보고 이전 상태를 재현할까요?

Binlog란

Binary Log(Binlog)는 MySQL 서버에서 발생하는 모든 데이터 변경 이벤트를 순서대로 기록한 로그 파일입니다.

PLAINTEXT
Binlog의 두 가지 핵심 용도:
1. 복제 (Replication): 소스 서버의 변경을 복제본에 전달
2. 시점 복구 (Point-in-Time Recovery): 백업 이후의 변경을 재적용

Binlog는 InnoDB의 Redo Log와 다릅니다. Redo Log는 스토리지 엔진 레벨의 물리적 로그이고, Binlog는 MySQL 서버 레벨의 논리적 로그입니다.

구분BinlogRedo Log
레벨MySQL 서버InnoDB 엔진
기록 대상논리적 변경 (SQL 또는 행 데이터)물리적 변경 (페이지 수정)
용도복제, 시점 복구크래시 복구
순환파일이 계속 생성됨고정 크기 순환

Binlog 설정

SQL
-- Binlog 활성화 확인 (MySQL 8.0에서는 기본 ON)
SHOW VARIABLES LIKE 'log_bin';

-- Binlog 파일 목록
SHOW BINARY LOGS;

-- 현재 Binlog 파일과 포지션
SHOW MASTER STATUS;
INI
# my.cnf 설정
[mysqld]
log_bin = /var/lib/mysql/mysql-bin
server_id = 1
binlog_expire_logs_seconds = 604800  # 7일 보관
max_binlog_size = 1073741824         # 1GB 단위로 파일 교체

Binlog 포맷 — STATEMENT vs ROW vs MIXED

STATEMENT 포맷

실행된 SQL 문장을 그대로 기록합니다.

SQL
SET GLOBAL binlog_format = 'STATEMENT';
PLAINTEXT
# Binlog 내용 예시
UPDATE orders SET status = 'shipped' WHERE id = 100;

장점:

  • 로그 크기가 작습니다 (SQL 한 줄)
  • 사람이 읽기 쉽습니다

단점:

  • 비결정적 함수 문제: NOW(), UUID(), RAND() 등이 소스와 복제본에서 다른 값을 반환할 수 있습니다
  • LIMIT 없는 UPDATE/DELETE에서 행 순서가 달라질 수 있습니다
SQL
-- 위험한 예시
INSERT INTO logs (id, created_at) VALUES (1, NOW());
-- 소스와 복제본에서 NOW()의 결과가 다를 수 있음

ROW 포맷 (권장)

변경된 행의 **이전 값(before image)**과 **이후 값(after image)**을 기록합니다.

SQL
SET GLOBAL binlog_format = 'ROW';
PLAINTEXT
# Binlog 내용 예시 (바이너리 → mysqlbinlog로 디코딩)
### UPDATE orders
### WHERE
###   @1=100           -- id (before)
###   @2='pending'     -- status (before)
### SET
###   @1=100           -- id (after)
###   @2='shipped'     -- status (after)

장점:

  • 비결정적 함수 문제가 없습니다 (결과 값을 기록)
  • 데이터 일관성이 보장됩니다
  • MySQL 8.0 기본 포맷

단점:

  • 로그 크기가 클 수 있습니다 (대량 UPDATE 시)
  • 사람이 바로 읽기 어렵습니다

binlog_row_image 옵션

ROW 포맷에서 기록되는 데이터 양을 조절합니다.

SQL
SHOW VARIABLES LIKE 'binlog_row_image';
기록 내용
full (기본)모든 컬럼의 전후 값
minimal변경에 필요한 최소 컬럼만
noblobBLOB/TEXT 컬럼을 변경하지 않았으면 제외

minimal을 사용하면 Binlog 크기를 크게 줄일 수 있지만, 시점 복구 시 제약이 있을 수 있습니다.

MIXED 포맷

기본적으로 STATEMENT를 사용하고, 비결정적 함수가 포함된 쿼리만 ROW로 전환합니다.

SQL
SET GLOBAL binlog_format = 'MIXED';

실무에서는 ROW 포맷이 가장 안전하고 널리 사용됩니다.

Binlog 포지션

Binlog에서 특정 이벤트의 위치를 식별하는 방법입니다.

SQL
-- 현재 포지션 확인
SHOW MASTER STATUS;
-- File: mysql-bin.000003, Position: 1234
PLAINTEXT
Binlog 파일 구조:
mysql-bin.000001  ← 파일명
├─ Position 4: Format Description Event
├─ Position 124: GTID Event
├─ Position 200: Query Event (BEGIN)
├─ Position 350: Table Map Event
├─ Position 400: Write Rows Event (INSERT)
├─ Position 600: Xid Event (COMMIT)
└─ ...

복제에서는 "어느 파일의 어느 포지션까지 적용했는지"를 추적합니다. 하지만 이 방식은 서버 토폴로지가 변경되면 관리가 복잡해집니다.

GTID — 글로벌 트랜잭션 ID

GTID란

MySQL 5.6에서 도입된 GTID는 각 트랜잭션에 전역적으로 고유한 ID를 부여합니다.

PLAINTEXT
GTID 형식: server_uuid:transaction_id
예: 3E11FA47-71CA-11E1-9E33-C80AA9429562:42
SQL
-- GTID 활성화
SHOW VARIABLES LIKE 'gtid_mode';       -- ON
SHOW VARIABLES LIKE 'enforce_gtid_consistency';  -- ON

GTID의 장점

  1. 포지션 관리 불필요: 파일명과 포지션 대신 GTID로 복제 상태를 추적합니다
  2. 토폴로지 변경 용이: 마스터 전환(Failover) 시 복제본이 자동으로 올바른 위치를 찾습니다
  3. 복제 갭 감지: 어떤 트랜잭션이 누락되었는지 GTID 셋으로 확인할 수 있습니다
SQL
-- 현재 서버에서 실행된 GTID 셋
SHOW VARIABLES LIKE 'gtid_executed';
-- 3E11FA47-71CA-11E1-9E33-C80AA9429562:1-1000

-- 복제본에서 적용된 GTID 셋
SELECT @@GLOBAL.gtid_executed;

GTID 기반 복제 설정

SQL
-- 복제본에서
CHANGE REPLICATION SOURCE TO
    SOURCE_HOST = '10.0.1.1',
    SOURCE_USER = 'repl_user',
    SOURCE_PASSWORD = 'password',
    SOURCE_AUTO_POSITION = 1;  -- GTID 사용

START REPLICA;

SOURCE_AUTO_POSITION = 1을 사용하면 파일명/포지션을 지정하지 않아도 GTID를 기반으로 자동으로 올바른 위치를 찾습니다.

mysqlbinlog 도구

Binlog 파일을 읽고, 분석하고, 재실행할 수 있는 명령줄 도구입니다.

기본 사용법

BASH
# Binlog 내용 출력
mysqlbinlog mysql-bin.000001

# ROW 포맷을 읽기 좋게 디코딩
mysqlbinlog --verbose mysql-bin.000001

# 특정 시간 범위만 추출
mysqlbinlog --start-datetime="2026-03-19 10:00:00" \
            --stop-datetime="2026-03-19 11:00:00" \
            mysql-bin.000001

# 특정 포지션 범위만 추출
mysqlbinlog --start-position=1234 \
            --stop-position=5678 \
            mysql-bin.000001

# 특정 데이터베이스만 필터
mysqlbinlog --database=myapp mysql-bin.000001

시점 복구 (Point-in-Time Recovery)

실수로 데이터를 삭제한 경우의 복구 과정입니다.

BASH
# 1. 전체 백업을 먼저 복원
mysql < full_backup.sql

# 2. 백업 이후부터 실수 직전까지의 Binlog를 적용
mysqlbinlog --start-position=백업_포지션 \
            --stop-datetime="2026-03-19 14:59:59" \
            mysql-bin.000005 | mysql

# 3. 실수 쿼리 이후부터 현재까지의 Binlog를 적용
mysqlbinlog --start-datetime="2026-03-19 15:00:01" \
            mysql-bin.000005 | mysql

Binlog 관리

파일 보관 기간

SQL
-- 보관 기간 설정 (초 단위, 기본 2592000 = 30일)
SET GLOBAL binlog_expire_logs_seconds = 604800;  -- 7일

-- 수동 삭제
PURGE BINARY LOGS TO 'mysql-bin.000005';
PURGE BINARY LOGS BEFORE '2026-03-10 00:00:00';

파일 크기 제한

SQL
-- 파일당 최대 크기 (기본 1GB)
SHOW VARIABLES LIKE 'max_binlog_size';

-- 수동으로 새 파일로 교체
FLUSH BINARY LOGS;

sync_binlog 설정

SQL
-- 트랜잭션 커밋 시 Binlog를 디스크에 동기화하는 빈도
SHOW VARIABLES LIKE 'sync_binlog';
동작성능안전성
0OS에 위임빠름낮음
1 (권장)매 커밋마다 fsync느림높음
NN번 커밋마다 fsync중간중간

sync_binlog = 1innodb_flush_log_at_trx_commit = 1을 함께 설정하면 가장 안전합니다.

정리

  • Binlog는 MySQL의 모든 데이터 변경을 기록하며, 복제와 시점 복구에 필수입니다
  • ROW 포맷이 가장 안전하며 MySQL 8.0의 기본 포맷입니다
  • GTID를 사용하면 파일/포지션 관리가 불필요해지고 복제 토폴로지 변경이 쉬워집니다
  • mysqlbinlog 도구로 Binlog를 분석하고 시점 복구를 수행할 수 있습니다
댓글 로딩 중...