mysql迁移不能只靠select...for update,因其仅对已存在行加行锁、无法防ddl和跨表操作;应优先用flush tables with read lock或super_read_only实现全局写入阻断,配合双写+校验保障一致性。

MySQL 迁移时为什么不能只靠 SELECT ... FOR UPDATE
因为数据迁移往往跨表、跨库甚至跨实例,SELECT ... FOR UPDATE 只在当前事务内对 InnoDB 行加锁,且仅对已存在行生效——对 INSERT 或新主键值不设防。更关键的是,它无法阻止其他会话执行 DROP TABLE、TRUNCATE 或 DDL 操作。
常见错误现象:ERROR 1205 (40001): Deadlock found when trying to get lock 频发,或迁移中途被另一个 ALTER TABLE 中断,导致数据不一致。
- 真正需要的是「阻断写入」而非「争抢行锁」:优先用
FLUSH TABLES WITH READ LOCK(全局读锁),但注意它会阻塞所有更新,包括INSERT、UPDATE、DELETE和大部分 DDL - 如果业务无法接受长时全局锁,改用
LOCK TABLES table_name READ,但仅对指定表生效,且不阻止其他表的写入,容易漏锁 -
FLUSH TABLES WITH READ LOCK在 MySQL 8.0+ 主从环境中可能被复制线程绕过,需确认SHOW PROCESSLIST中无Replication_IO或Replication_SQL线程正在活跃写入
mysqldump 迁移时要不要加 --single-transaction
要看存储引擎和一致性要求。--single-transaction 依赖 InnoDB 的 MVCC,只对 InnoDB 表起作用,对 MyISAM 或含非事务表的库完全无效;它也不锁表,但无法保证「整个 dump 过程中所有表逻辑一致」。
使用场景:源库全为 InnoDB、无 DDL 变更、允许短暂主从延迟增大。
- 加了
--single-transaction后,dump 开始时刻会触发一次START TRANSACTION WITH CONSISTENT SNAPSHOT,后续读取都基于该快照——但若期间有ALTER TABLE,会导致mysqldump报错ERROR 2013 (HY000): Lost connection to MySQL server during query - 如果库中混有 MyISAM 表,
--single-transaction自动失效,mysqldump会退回到逐表LOCK TABLES ... READ,此时必须配合--lock-all-tables才能保全局一致 - 性能影响:开启后不会阻塞写入,但大表上 snapshot 维护开销略增,尤其高并发更新下
UNDO LOG膨胀更快
线上不停服迁移,如何安全替换表而不丢数据
核心不是“锁得更死”,而是“切流+双写+校验”的协同。直接 RENAME TABLE 替换表名看似快,但若应用缓存未清、连接池未重连、或中间件路由未刷新,极易查到旧表或写入失败。
真实风险点:rename 瞬间,应用层可能正执行 INSERT INTO old_table,而该语句已发到服务端但尚未落盘,rename 后变成对不存在表的操作,报 ERROR 1146 (42S02): Table 'db.old_table' doesn't exist。
- 推荐流程:先建新表 → 应用双写(写 old + 写 new)→ 全量同步 old 到 new → 校验一致性 → 切读流量到 new → 停写 old → 最后
RENAME或DROP - 双写阶段务必用同一事务包裹,例如:
BEGIN; INSERT INTO old_table ...; INSERT INTO new_table ...; COMMIT;,否则出现部分写入就不可逆 - 避免用
INSERT IGNORE或ON DUPLICATE KEY UPDATE做双写兜底——它们掩盖冲突,导致新旧表主键/唯一索引不一致却无感知
GTID 模式下做主从切换迁移,SET GLOBAL super_read_only = ON 为什么比 FLUSH TABLES WITH READ LOCK 更稳
因为 super_read_only 是实例级写入熔断,不受事务隔离级别、存储引擎类型、甚至用户权限(如 REPLICATION_SLAVE_ADMIN)影响,且不依赖客户端连接状态。
对比 FLUSH TABLES WITH READ LOCK:后者在 GTID 环境中若从库 IO 线程卡住,可能导致主库 FLUSH 一直等待,最终超时失败;而 super_read_only 立即生效,且不影响从库 SQL 线程回放。
- 启用前先确认
SELECT @@gtid_mode返回ON,否则super_read_only对 GTID 复制无意义 -
super_read_only = ON不影响SET类语句(如SET NAMES utf8mb4),但会拒绝所有 DML 和 DDL,包括CREATE TEMPORARY TABLE - 切回可写时,别只执行
SET GLOBAL super_read_only = OFF,还要检查SELECT @@read_only是否也为OFF,否则仍被拦截











