한 줄 요약: PK 없는 InnoDB 테이블이 일으키는 RBR 복제 지연·운영 도구 비호환을 MySQL 8.0 이 두 가지 정반대 방향의 변수로 풀어줌.
먼저 도입된
sql_require_primary_key(8.0.13) 는 PK 빠진CREATE/ALTER TABLE을 거부함. 그 뒤를 잇는 GIPK (sql_generate_invisible_primary_key, 8.0.30) 는 반대 방향으로 PK 없는 새 InnoDB 테이블에my_row_id를INVISIBLE·AUTO_INCREMENT로 자동 부여함.두 변수는 함께 켤 수 있고, 레플리카 측 적용 정책은 채널 옵션
REQUIRE_TABLE_PRIMARY_KEY_CHECK가 따로 통제함.
개요
InnoDB 는 클러스터 인덱스 구조라 PK 가 없는 테이블이면 내부적으로 DB_ROW_ID (6바이트 hidden 컬럼)를 클러스터 인덱스 키로 씀. 이 값은 binlog 에 기록되지 않기 때문에 Row-Based Replication 환경에서 레플리카가 변경 대상 행을 찾을 때 PK·세컨더리 인덱스 대신 풀스캔·HASH_SCAN 경로로 빠짐. 이게 PK 없는 테이블에서 자주 발생하는 복제 지연의 근본 원인임.
MySQL 8.0 은 이 문제에 두 가지 정반대 방향의 옵션을 차례로 도입함 — 먼저 PK 없으면 DDL 자체를 거부 하는 sql_require_primary_key (8.0.13), 그 뒤에 PK 없으면 자동으로 붙여주는 GIPK (8.0.30). 두 변수는 충돌하지 않고 함께 켤 수 있음. GIPK 가 먼저 PK 를 붙이면 sql_require_primary_key 검사도 자연스럽게 통과함.
레플리카는 이 정책을 자동 상속하지 않음. 어느 쪽 정책을 받을지는 채널별 옵션 REQUIRE_TABLE_PRIMARY_KEY_CHECK 로 별도 통제함.
| 변수·옵션 | 도입 | 기본값 | 정책·동작 |
|---|---|---|---|
sql_require_primary_key | 8.0.13 | OFF | PK 빠진 CREATE/ALTER TABLE 을 에러 3750 으로 거부 |
sql_generate_invisible_primary_key | 8.0.30 (Release Notes) | OFF | PK 없는 새 InnoDB 테이블에 my_row_id (BIGINT UNSIGNED AUTO_INCREMENT INVISIBLE) 자동 PK 부여 |
REQUIRE_TABLE_PRIMARY_KEY_CHECK | CHANGE REPLICATION SOURCE TO 채널 옵션 | STREAM | 레플리카가 위 두 정책을 어떻게 받을지 결정 — STREAM·ON·OFF·GENERATE |
결론
두 옵션의 정책 비교
| 구분 | sql_require_primary_key | GIPK (sql_generate_invisible_primary_key) |
|---|---|---|
| 도입 버전 | 8.0.13+ | 8.0.30+ |
| 정책 방향 | PK 없으면 거부 (에러 3750) | PK 없으면 자동 부여 (서버가 보충) |
| 적용 시점 | 모든 CREATE/ALTER TABLE | 새 InnoDB 테이블 생성 시 |
| 기존 테이블 | 변수 ON 직후엔 그대로, 이후 ALTER 부터 검사 적용 | 소급 적용 X (ALTER TABLE 직접 필요) |
| 부여되는 PK | 없음 (개발자가 명시) | my_row_id BIGINT UNSIGNED AUTO_INCREMENT INVISIBLE |
| 함께 켤 때 | 동일 | GIPK 가 먼저 PK 부여 → sql_require_primary_key 검사 통과 |
언제 쓰는가
sql_require_primary_key가 어울리는 상황 — 정책적으로 PK 가 빠진 DDL 을 아예 받지 말아야 하는 환경 (감사·컴플라이언스, 또는 PK 누락이 운영에 들어오는 것 자체를 휴먼 게이트로 막고 싶은 팀). 자동 보충 없이 책임을 개발자에게 명시적으로 떠넘김.- GIPK 가 어울리는 상황 — 레거시·서드파티 툴이 종종 PK 없이 테이블을 만들고, 그게 RBR 복제 지연이나
pt-online-schema-change/gh-ost호환 문제로 번지는 환경. 개발자 부담 없이 서버가 자동으로 메워주는 쪽. 신규 테이블에만 적용되므로 일괄 재생성이 전제. - 둘 다 켤 때 (가장 안전) — 기본 정책은 PK 명시 강제 로 두되, 깜빡한 케이스는 GIPK 가 자동 보충. 단 GIPK 가 추가한
my_row_id가SHOW CREATE TABLE·mysqldump결과에 나타나는 점은 팀 합의 필요. 레플리카에는REQUIRE_TABLE_PRIMARY_KEY_CHECK = GENERATE와 조합 가능.
운영 시 체크
- GIPK 는 새로 생성되는 테이블에만 적용됨. 기존 테이블에는 별도
ALTER TABLE ... ADD COLUMN ... PRIMARY KEY가 필요. - 스키마 관리 도구(Liquibase, Flyway 등) 의 diff 가 달라질 수 있음.
SHOW CREATE TABLE결과에my_row_id가 추가되므로 팀 마이그레이션 전에 도구 동작 테스트 필요. - 레플리카에서도 GIPK 를 자동 생성하려면
CHANGE REPLICATION SOURCE TO REQUIRE_TABLE_PRIMARY_KEY_CHECK = GENERATE가 별도로 필요함. 소스에서 ON 이어도 레플리카는 자동 상속하지 않음. - GIPK ON 상태에서
CREATE TABLE ... SELECT의 statement-based 복제는 미지원. 8.4 공식 docs 가 명시하는 제약으로,binlog_format=STATEMENT/MIXED환경에서 CTAS 가 들어오면 복제가 깨짐. ROW 포맷이면 무관하나, SBR 잔존 환경에서 GIPK 를 켤 때는 사전 점검 필요.
GIPK 설정 확인 및 활성화
(sql_require_primary_key 도 SET 패턴은 동일함 — 변수명만 바꿔서 적용. 아래는 GIPK 기준.)
현재 설정 확인
SHOW VARIABLES LIKE 'sql_generate_invisible_primary_key';+------------------------------------+-------+
| Variable_name | Value |
+------------------------------------+-------+
| sql_generate_invisible_primary_key | OFF |
+------------------------------------+-------+활성화
-- 세션 단위 (테스트·임시 적용)
SET sql_generate_invisible_primary_key = ON;
-- 전역 (온라인, 재시작 시 원복)
SET GLOBAL sql_generate_invisible_primary_key = ON;영구 반영은 my.cnf 에 기재:
[mysqld]
sql_generate_invisible_primary_key = ONMySQL 8.4.8 에서 직접 확인 —
SET GLOBAL은 즉시 반영되나 my.cnf 수정이 없으면 재시작 시 원복됨.
GIPK 동작 확인
아래 모든 출력은 MySQL 8.4.8 컨테이너에서 직접 실행한 결과임. sql_require_primary_key 의 동작(에러 3750 거부)은 아래 레플리카 설정 의 ON 시나리오에서 같은 형태로 재현됨.
대조군 — GIPK OFF 에서는 PK 없이 생성
SET sql_generate_invisible_primary_key = OFF;
CREATE TABLE without_gipk (c1 INT, c2 VARCHAR(100));
SHOW CREATE TABLE without_gipk\G*************************** 1. row ***************************
Table: without_gipk
Create Table: CREATE TABLE `without_gipk` (
`c1` int DEFAULT NULL,
`c2` varchar(100) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ciPK 없이 생성됨 — InnoDB 내부적으로 DB_ROW_ID 를 씀.
GIPK ON — my_row_id PK 자동 추가
SET sql_generate_invisible_primary_key = ON;
CREATE TABLE with_gipk (c1 INT, c2 VARCHAR(100));
SHOW CREATE TABLE with_gipk\G*************************** 1. row ***************************
Table: with_gipk
Create Table: CREATE TABLE `with_gipk` (
`my_row_id` bigint unsigned NOT NULL AUTO_INCREMENT /*!80023 INVISIBLE */,
`c1` int DEFAULT NULL,
`c2` varchar(100) DEFAULT NULL,
PRIMARY KEY (`my_row_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_cimy_row_id 는 BIGINT UNSIGNED NOT NULL AUTO_INCREMENT 이면서 INVISIBLE 이고 PK 로 지정됨. 주석 /*!80023 INVISIBLE */ 는 MySQL 8.0.23 이상에서만 해석되는 버전 조건부 힌트임.
INVISIBLE 컬럼 동작 — SELECT * 에서는 숨겨짐
INSERT INTO with_gipk (c1, c2) VALUES (1, 'alpha'), (2, 'beta'), (3, 'gamma');
SELECT * FROM with_gipk;+------+-------+
| c1 | c2 |
+------+-------+
| 1 | alpha |
| 2 | beta |
| 3 | gamma |
+------+-------+my_row_id 값은 지정하지 않았으나 AUTO_INCREMENT 로 채워졌고, SELECT * 에는 나타나지 않음. 기존 애플리케이션 코드가 그대로 동작하는 이유임.
명시 조회 — INVISIBLE 컬럼도 볼 수 있음
SELECT my_row_id, c1, c2 FROM with_gipk;+-----------+------+-------+
| my_row_id | c1 | c2 |
+-----------+------+-------+
| 1 | 1 | alpha |
| 2 | 2 | beta |
| 3 | 3 | gamma |
+-----------+------+-------+컬럼명을 명시하면 조회 가능. 1부터 순차 증가하며, 이후 RBR 환경에서 binlog 에도 이 값이 실리므로 레플리카가 행을 O(log n) 으로 바로 찾을 수 있음.
레플리카 설정 (REQUIRE_TABLE_PRIMARY_KEY_CHECK)
먼저 한 짝인 sql_require_primary_key 부터 — GIPK 가 PK 없으면 자동으로 붙임 정책이라면 이쪽은 PK 없으면 DDL 자체를 막음 정책임. MySQL 8.0.13 도입, 기본값 OFF, Global·Session 동적 변경 가능.
ON 이면 PK 빠진 CREATE TABLE/ALTER TABLE 을 에러 3750 으로 거부함. GIPK 와 함께 ON 으로 켜도 GIPK 가 먼저 PK 를 붙여 통과시키므로 충돌 없음.
REQUIRE_TABLE_PRIMARY_KEY_CHECK 는 레플리카가 소스의 sql_require_primary_key 를 어떻게 받을지 결정하는 채널별 옵션임. 소스에서 GIPK 가 활성화돼 있어도 레플리카는 이 옵션을 자동 상속하지 않음.
아래 결과는 모두 MySQL 8.4.8 × 2 (소스·레플리카, GTID, Row-Based Replication) 토폴로지에서 직접 실측한 값임.
값과 동작
| 값 | 동작 | 실측 결과 |
|---|---|---|
STREAM | 기본값. 트랜잭션별로 소스의 sql_require_primary_key 값을 그대로 따름 | 소스 기본값(sql_require_primary_key=OFF)에서는 PK 없는 테이블이 그대로 복제됨 |
OFF | 소스 설정 무시, PK 없는 DDL 허용 | 레플리카에 PK 없는 테이블로 생성 |
ON | 소스 설정 무시, PK 없는 DDL 은 에러 | 에러 3750 으로 SQL thread 중단, 테이블 생성되지 않음 |
GENERATE | PK 없는 DDL 을 받으면 레플리카에서 my_row_id GIPK 를 붙여 생성 | 소스엔 PK 없고 레플리카에만 my_row_id — 토폴로지 간 구조가 달라짐 |
현재 값은 performance_schema 에서 조회:
SELECT REQUIRE_TABLE_PRIMARY_KEY_CHECK
FROM performance_schema.replication_applier_configuration;값 변경:
STOP REPLICA;
CHANGE REPLICATION SOURCE TO REQUIRE_TABLE_PRIMARY_KEY_CHECK = GENERATE;
START REPLICA;ON — SQL thread 중단
레플리카를 ON 으로 걸어둔 뒤 소스에서 PK 없는 테이블을 만들면 레플리카는 에러 3750 으로 SQL thread 를 멈춤 (performance_schema.replication_applier_status_by_worker.LAST_ERROR_MESSAGE).
Worker 1 failed executing transaction '...:14' at source log mysql-bin.000003,
end_log_pos 2050; Error 'Unable to create or change a table without a primary
key, when the system variable 'sql_require_primary_key' is set. Add a primary
key to the table or unset this variable to avoid this message. ...' on query.
Default database: 'testdb'. Query: 'CREATE TABLE t_on (c1 INT, c2 VARCHAR(20))'복구는 문제 GTID 를 빈 트랜잭션으로 덮어 건너뛰는 방식:
STOP REPLICA;
SET GTID_NEXT='<문제 GTID>';
BEGIN; COMMIT;
SET GTID_NEXT=AUTOMATIC;
START REPLICA;건너뛴 DDL 은 복구 후 수동으로 PK 를 붙여 재실행해야 함.
GENERATE — 소스·레플리카 구조가 달라짐
소스가 GIPK OFF 인 상태에서 PK 없이 테이블을 만들어도, GENERATE 를 건 레플리카는 자동으로 my_row_id 를 붙여 생성함.
소스의 SHOW CREATE TABLE t_gen:
CREATE TABLE `t_gen` (
`c1` int DEFAULT NULL,
`c2` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci레플리카:
CREATE TABLE `t_gen` (
`my_row_id` bigint unsigned NOT NULL AUTO_INCREMENT /*!80023 INVISIBLE */,
`c1` int DEFAULT NULL,
`c2` varchar(20) DEFAULT NULL,
PRIMARY KEY (`my_row_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci레플리카가 자체 AUTO_INCREMENT 로 채운 값:
+-----------+----+----+
| my_row_id | c1 | c2 |
+-----------+----+----+
| 1 | 10 | x |
| 2 | 20 | y |
| 3 | 30 | z |
+-----------+----+----+GENERATE 운영 시 주의:
-
소스·레플리카 스키마 분기 — 소스엔
my_row_id가 없고 레플리카에만 있음. 이 상태가 두 가지 경로로 문제를 만듦.- 레플리카가 새 소스로 승격되면 binlog 에
my_row_id가 실려 하위 레플리카 RBR 이 깨질 수 있음. - Liquibase/Flyway 같은 diff 도구를 레플리카에 돌리면 차이를 오탐함.
안전한 길은 토폴로지 전체를 동일하게
GENERATE로 맞추거나, 차라리 소스에서sql_generate_invisible_primary_key = ON을 켜 스키마를 통일하는 쪽임. - 레플리카가 새 소스로 승격되면 binlog 에
-
my_row_id값은 노드 간에 일치하지 않음 — 각 레플리카가 독립적으로AUTO_INCREMENT를 채우므로 같은 행이라도 값이 다름. 애플리케이션이my_row_id를 식별자로 쓰면 안 됨.
언제 어느 값을 쓰나
STREAM(기본) — 토폴로지 전체가 동일한sql_require_primary_key설정을 공유한다는 확신이 있을 때. 일반적인 운영 구성.ON— 소스 설정과 무관하게 레플리카에서 독립적으로 PK 강제 정책을 걸고, 위반 시 에러로 막고 DBA 가 수동 개입하는 운영을 원할 때.GENERATE— PK 없는 테이블이 섞여 들어오는 환경에서 복제 지연만은 해소하고 싶을 때. 위 세 가지 주의점을 감당 가능한 경우에만.OFF— 소스 설정을 무시하고 PK 없는 DDL 을 무조건 허용. 일반 운영에서는 권장되지 않음.
MySQL 8.4.8 × 2 (GTID, Row-Based Replication) 실측. 기본값이
STREAM이며OFF가 아니라는 점,ON의 에러 코드가3750이라는 점,GENERATE는 레플리카에만my_row_id를 붙여 소스·레플리카 스키마가 의도적으로 어긋난다는 점을 확인함.