本地跑得好好的备份 SQL,上了 RDS 直接 1786 报错
发版前备份旧表是标准流程:CREATE TABLE xxx_bak AS SELECT * FROM xxx,本地 MySQL 跑顺溜了几个月。某次切流窗口,同样一条 SQL 打到阿里云 RDS,立刻报错,整个迁移脚本卡死:
ERROR 1786 (HY000): CREATE TABLE ... SELECT is forbidden
when @@GLOBAL.ENFORCE_GTID_CONSISTENCY = 1.
窗口期有限,慌了。
现象
错误只在云 RDS(阿里云 RDS MySQL 5.7 / AWS RDS MySQL 5.7+)上复现。本地用同版本 MySQL 执行毫无问题。日志只有这一行 1786,没有其他上下文。
常见触发场景:
- 发版前备份缓存/临时表:
CREATE TABLE cache_bak AS SELECT * FROM cache - 数据修复脚本保留原表快照
- 运维手工拷贝表做线上数据比对
根因
MySQL GTID 模式要求每个事务只产生一个 GTID。CREATE TABLE ... SELECT 看起来是一条语句,实际在 binlog 层面会写入两个独立事件:一个 DDL(CREATE)+ 一个 DML(INSERT SELECT)。两个事件 → 两个 GTID → 破坏复制协议的一致性保证。
云厂商 RDS 的多可用区、只读实例全部依赖 GTID 进行主从同步,所以默认强制开启 enforce_gtid_consistency=ON 且不允许用户修改。
可以用下面的 SQL 确认实例状态:
SHOW VARIABLES LIKE 'gtid%';
-- enforce_gtid_consistency = ON → 禁用 CREATE AS SELECT
-- gtid_mode = ON / ON_PERMISSIVE → GTID 已启用本地 MySQL 默认 gtid_mode=OFF,CREATE TABLE ... SELECT 完全合法。开发者往往在本地、CI 都跑通了才发现云 RDS 禁用这条语法——而此时往往已经在发版窗口内,压力最大。另外,试图临时关掉 GTID 也走不通:云 RDS 禁止 SET GLOBAL enforce_gtid_consistency=OFF,权限直接被拒。
正解
把一条混合语句拆成两条独立事务:
-- Step 1:只建结构(单一 DDL 事务,获得一个 GTID)
CREATE TABLE orders_bak LIKE orders;
-- Step 2:拷数据(独立 DML 事务,获得另一个 GTID)
INSERT INTO orders_bak SELECT * FROM orders;
-- Step 3(可选):加时间戳后缀标注备份时点
RENAME TABLE orders_bak TO orders_bak_20260417;两条语句各自获得独立 GTID,RDS 不再拒绝,复制链路完全正常。
任何"备份当前表 + 清空/迁移"的脚本,可以套这个模板:
-- 1. GTID-safe 备份
CREATE TABLE {target}_bak LIKE {target};
INSERT INTO {target}_bak SELECT * FROM {target};
-- 2. 清空或结构变更
TRUNCATE TABLE {target};
-- 或:ALTER TABLE {target} ADD COLUMN ...CREATE LIKE 只复制表结构(纯 DDL),INSERT SELECT 只复制数据(纯 DML),两条语句独立事务、独立 GTID,完全符合 RDS GTID 一致性要求。RENAME 同样是纯 DDL,可以安全附加。
排查流程
写迁移/备份脚本
↓
SELECT @@GLOBAL.enforce_gtid_consistency;
↓
结果 = ON? ──是──→ 必须用 CREATE LIKE + INSERT SELECT
│
否(本地开发环境)
↓
可用 CREATE AS SELECT(但仍建议拆分,生产兼容且可读性更好)
一句话外卖
在云 RDS 上,
CREATE TABLE ... SELECT是永久禁区;养成「先CREATE LIKE,再INSERT SELECT」的习惯,本地和生产行为一致,再也不怕 1786。