데이터 이관이나 복제 구성 후에 데이터가 동일한지 어떻게 확인할까?
보통 row count부터 맞춰보는데, 건수가 같다고 데이터가 같은 건 아니다.
그래서 MySQL/MariaDB에 있는 checksum table을 테스트해본 내용을 정리해본다.
checksum table은 테이블 전체 데이터를 읽어서 하나의 checksum 값을 돌려준다.
원리는 간단하다. 행마다 CRC를 계산하고 그걸 다 더한다.
(CRC는 데이터를 정해진 다항식으로 나눈 나머지 값으로, 데이터가 1비트만 바뀌어도 값이 달라져서 변경 여부를 빠르게 확인할 때 쓴다. MySQL의 CRC32()는 32비트짜리 값을 돌려준다.)
그래서 같은 데이터라면 서버가 달라도 같은 값이 나와야 한다. (물론 조건이 있다. 뒤에서 다룸)
테스트용 테이블부터 만든다.
CREATE TABLE t1 (
id INT PRIMARY KEY,
name VARCHAR(50),
amount DECIMAL(10,2),
memo VARCHAR(100) NULL,
reg_dt DATETIME
) ENGINE=InnoDB;
INSERT INTO t1 VALUES
(1,'kim',100.00,NULL,'2026-10-01 10:00:00'),
(2,'lee',200.50,'', '2026-10-02 11:00:00'),
(3,'park',300.00,'x','2026-10-03 12:00:00');
-- 비교용 테이블
CREATE TABLE t2 LIKE t1;
INSERT INTO t2 SELECT * FROM t1;
-- checksum 결과
MariaDB [gwchu]> checksum table t1, t2;
+----------+------------+
| Table | Checksum |
+----------+------------+
| gwchu.t1 | 1257747654 |
| gwchu.t2 | 1257747654 |
+----------+------------+
2 rows in set (0.000 sec)
checksum table은 여러 테이블을 한 번에 넣어서 바로 비교가 가능하다.
참고로 checksum table에는 quick 옵션도 있는데, 이건 MyISAM에서만 의미가 있다.
MyISAM은 checksum=1 옵션을 주면 checksum 값을 미리 저장해두고 변경 때마다 갱신하는 live checksum을 지원해서, quick으로 저장된 값을 바로 꺼내올 수 있다.
InnoDB는 live checksum이 없어서 QUICK을 주면 NULL이 나오고, 결국 매번 전체 스캔으로 계산한다.
데이터 변경시 checksum이 모두 동일한지에 대해서 테스트한 결과이다.
--------------------------- 데이터 변경 테스트
MariaDB [gwchu]> UPDATE t2 SET amount = 200.51 WHERE id = 2;
MariaDB [gwchu]> checksum table t1, t2;
+----------+------------+
| Table | Checksum |
+----------+------------+
| gwchu.t1 | 1257747654 |
| gwchu.t2 | 2511561321 |
+----------+------------+
-- 원복시 checksum 동일해질까?
MariaDB [gwchu]> UPDATE t2 SET amount = 200.50 WHERE id = 2;
MariaDB [gwchu]> checksum table t1, t2;
+----------+------------+
| Table | Checksum |
+----------+------------+
| gwchu.t1 | 1257747654 |
| gwchu.t2 | 1257747654 |
+----------+------------+
--------------------------- NULL과 빈 문자열 구분 테스트
MariaDB [gwchu]> UPDATE t2 SET memo = '' WHERE id = 1;
MariaDB [gwchu]> select * from t1;
+----+------+--------+------+---------------------+
| id | name | amount | memo | reg_dt |
+----+------+--------+------+---------------------+
| 1 | kim | 100.00 | NULL | 2026-10-01 10:00:00 |
| 2 | lee | 200.50 | | 2026-10-02 11:00:00 |
| 3 | park | 300.00 | x | 2026-10-03 12:00:00 |
+----+------+--------+------+---------------------+
MariaDB [gwchu]> select * from t2;
+----+------+--------+------+---------------------+
| id | name | amount | memo | reg_dt |
+----+------+--------+------+---------------------+
| 1 | kim | 100.00 | | 2026-10-01 10:00:00 |
| 2 | lee | 200.50 | | 2026-10-02 11:00:00 |
| 3 | park | 300.00 | x | 2026-10-03 12:00:00 |
+----+------+--------+------+---------------------+
MariaDB [gwchu]> checksum table t1, t2;
+----------+------------+
| Table | Checksum |
+----------+------------+
| gwchu.t1 | 1257747654 |
| gwchu.t2 | 1133446092 |
+----------+------------+
--------------------------- 대소문자 구분 테스트
MariaDB [gwchu]> UPDATE t2 SET name = 'KIM' WHERE id = 1;
MariaDB [gwchu]> select * from t1;
+----+------+--------+------+---------------------+
| id | name | amount | memo | reg_dt |
+----+------+--------+------+---------------------+
| 1 | kim | 100.00 | NULL | 2026-10-01 10:00:00 |
| 2 | lee | 200.50 | | 2026-10-02 11:00:00 |
| 3 | park | 300.00 | x | 2026-10-03 12:00:00 |
+----+------+--------+------+---------------------+
MariaDB [gwchu]> select * from t2;
+----+------+--------+------+---------------------+
| id | name | amount | memo | reg_dt |
+----+------+--------+------+---------------------+
| 1 | KIM | 100.00 | NULL | 2026-10-01 10:00:00 |
| 2 | lee | 200.50 | | 2026-10-02 11:00:00 |
| 3 | park | 300.00 | x | 2026-10-03 12:00:00 |
+----+------+--------+------+---------------------+
MariaDB [gwchu]> checksum table t1, t2;
+----------+------------+
| Table | Checksum |
+----------+------------+
| gwchu.t1 | 1257747654 |
| gwchu.t2 | 633912448 |
+----------+------------+
그런데, 컬럼이 추가되어 NULL로 들어가거나 row_format이 변경되어도 checksum은 동일하게 나왔지만,
컬럼 순서가 변경되었을 경우 checksum은 다르게 나온 결과를 확인할 수 있었다.
--------------------------- NULL 컬럼 추가
MariaDB [gwchu]> ALTER TABLE t2 ADD COLUMN extra INT NULL;
MariaDB [gwchu]> checksum table t1, t2;
+----------+------------+
| Table | Checksum |
+----------+------------+
| gwchu.t1 | 1257747654 |
| gwchu.t2 | 1257747654 |
+----------+------------+
MariaDB [gwchu]> select * from t1; select * from t2;
+----+------+--------+------+---------------------+
| id | name | amount | memo | reg_dt |
+----+------+--------+------+---------------------+
| 1 | kim | 100.00 | NULL | 2026-10-01 10:00:00 |
| 2 | lee | 200.50 | | 2026-10-02 11:00:00 |
| 3 | park | 300.00 | x | 2026-10-03 12:00:00 |
+----+------+--------+------+---------------------+
+----+------+--------+------+---------------------+-------+
| id | name | amount | memo | reg_dt | extra |
+----+------+--------+------+---------------------+-------+
| 1 | kim | 100.00 | NULL | 2026-10-01 10:00:00 | NULL |
| 2 | lee | 200.50 | | 2026-10-02 11:00:00 | NULL |
| 3 | park | 300.00 | x | 2026-10-03 12:00:00 | NULL |
+----+------+--------+------+---------------------+-------+
--------------------------- row_format 변경
MariaDB [gwchu]> ALTER TABLE t2 ROW_FORMAT=COMPACT;
MariaDB [gwchu]> checksum table t1, t2;
+----------+------------+
| Table | Checksum |
+----------+------------+
| gwchu.t1 | 1257747654 |
| gwchu.t2 | 1257747654 |
+----------+------------+
--------------------------- 컬럼 순서 변경
MariaDB [gwchu]> ALTER TABLE t2 MODIFY COLUMN memo VARCHAR(100) NULL AFTER id;
Query OK, 0 rows affected (0.005 sec)
Records: 0 Duplicates: 0 Warnings: 0
MariaDB [gwchu]> select * from t1; select * from t2;
+----+------+--------+------+---------------------+
| id | name | amount | memo | reg_dt |
+----+------+--------+------+---------------------+
| 1 | kim | 100.00 | NULL | 2026-10-01 10:00:00 |
| 2 | lee | 200.50 | | 2026-10-02 11:00:00 |
| 3 | park | 300.00 | x | 2026-10-03 12:00:00 |
+----+------+--------+------+---------------------+
3 rows in set (0.000 sec)
+----+------+------+--------+---------------------+
| id | memo | name | amount | reg_dt |
+----+------+------+--------+---------------------+
| 1 | NULL | kim | 100.00 | 2026-10-01 10:00:00 |
| 2 | | lee | 200.50 | 2026-10-02 11:00:00 |
| 3 | x | park | 300.00 | 2026-10-03 12:00:00 |
+----+------+------+--------+---------------------+
3 rows in set (0.000 sec)
MariaDB [gwchu]> checksum table t1, t2;
+----------+------------+
| Table | Checksum |
+----------+------------+
| gwchu.t1 | 1257747654 |
| gwchu.t2 | 3132562731 |
+----------+------------+
2 rows in set (0.000 sec)
운영에서 사용하려면 제일 중요한 건 락이다. checksum table 도는 동안 쓰기가 막히는지?
결론부터 말하면 InnoDB에서는 DML은 안 막히고 DDL만 대기한다.
checksum table은 일반 SELECT처럼 메타데이터 락을 SHARED_READ로 잡는다.
이건 DML이 잡는 SHARED_WRITE와 호환되니까 UPDATE, INSERT, DELETE는 그냥 진행된다.
행 락도 안 잡고 MVCC 스냅샷으로 읽는다.
대신 ALTER, TRUNCATE, DROP 같은 DDL은 checksum이 끝날 때까지 기다린다.
여기서 주의할 점이 하나 있다.
스냅샷으로 읽기 때문에 checksum 결과는 실행 시작 시점 기준이다. 도는 중에 들어온 UPDATE는 반영이 안 된다.
그래서 운영 중인 Master와 Slave를 checksum table로 비교하면, 시점이 안 맞아서 데이터가 같아도 값이 다르게 나올 수 있다.
필요하다면 PK 구간을 나눠서 직접 구간별로 계산하는 방법이 있긴 하다.
checksum table은 테이블 단위라 어디가 다른지는 모르고, 운영 중에는 Master와 Slave의 시점을 맞추기도 어렵다.
이걸 PK 구간 단위로 나눠서 복제 환경에서 자동으로 비교해주는 도구로 Percona Toolkit의 pt-table-checksum라는 것이 있다.
이 Tool의 원리는 다음과 같다.
실행하면 Source에 결과 저장용 percona.checksums 테이블이 생긴다.
Source는 PK 구간별로 checksum을 계산해서 이 테이블에 저장하는데, 이 쿼리가 복제되면서 Replica도 같은 쿼리를 자기 데이터로 계산한다.
결국 Replica에는 Source 값과 자기 값이 나란히 남고, 이걸 비교해서 다른 구간을 찾는다.
결과의 DIFFS가 0이 아니면 불일치가 있는 거다.
checksum table은 NULL과 '' 차이, 대소문자 차이까지 잡아내서 row count보다 훨씬 꼼꼼하게 볼 수 있다.
다만 컬럼 순서나 내부 저장 형식이 다르면 데이터가 같아도 값이 달라질 수 있고, 테이블 단위라 어디가 다른지는 알 수 없다.
운영 중인 복제 환경이라면 pt-table-checksum를 고려해볼만 할 것 같다.

'Database > RDBMS' 카테고리의 다른 글
| [MySQL] MySQL 5.7 -> 8.0 업그레이드 이슈 (0) | 2024.07.01 |
|---|---|
| [Oracle] Oracle express 21c 설치 (feat. Rocky 8.9) (0) | 2024.03.16 |
| [MySQL/MariaDB] my.cnf 메모리 설정 (0) | 2022.06.11 |
| [PostgreSQL] Architecture (0) | 2022.03.01 |
| [MariaDB] mariabackup 백업 & 복구 (0) | 2022.02.15 |




