全量迁移不能用select *因易oom、超时或断连;须用主键/时间戳游标分页、显式close、调优连接池;需记录binlog/lsn位点衔接增量;加唯一索引防重;用多值插入、禁用非必要索引提速;通过migration_checkpoint表实现断点续传。

全量迁移时为什么不能直接用 SELECT * 一次性拉取所有数据
因为源表数据量大(比如千万级以上),SELECT * 会把整张表加载进内存,Go 的 sql.Rows 默认不流式读取,容易触发 OOM;同时数据库连接可能超时,或被中间件(如 ProxySQL、MaxScale)主动断连。
实操建议:
- 必须分页查询,但避免用
OFFSET——大数据偏移性能急剧下降 - 改用基于主键/时间戳的游标分页:例如
WHERE id > ? ORDER BY id LIMIT 1000 - 每次查询后显式调用
rows.Close(),防止连接泄漏 - 使用
sql.DB.SetMaxOpenConns()和SetMaxIdleConns()控制连接池,避免打爆目标库
如何保证迁移过程中不丢数据、不重复写入
全量迁移不是“拷贝完就结束”,它必须和后续增量同步衔接。单纯靠迁移脚本无法应对迁移窗口期产生的新数据。
实操建议:
- 在迁移开始前,先记录源库当前 binlog 位点(MySQL)或 LSN(PostgreSQL),记为
start_pos - 全量导出完成后,立即启动增量同步工具(如
go-mysql-transfer或自研 binlog reader),从start_pos拉取变更 - 目标表加唯一索引(如
(id)或(biz_id, version)),配合INSERT IGNORE或ON CONFLICT DO NOTHING防重 - 不要依赖应用层“先查再插”——并发下必然冲突,且性能差
用 database/sql 批量插入时为什么特别慢
逐条 INSERT 是最常见性能陷阱。即使开了事务,每条语句仍需网络往返 + 解析 + 执行计划生成。
实操建议:
- 用
INSERT INTO ... VALUES (...), (...), (...)多值插入,单次最多 500~1000 行(取决于字段数和数据长度) - 对 PostgreSQL,优先用
pgx.CopyFrom();对 MySQL,可用LOAD DATA LOCAL INFILE(需服务端开启local_infile) - 禁用目标表所有非必要索引和外键约束(迁移后再重建),可提速 3–10 倍
- 事务粒度要平衡:太小(每 10 行 commit)导致日志刷盘频繁;太大(10 万行 commit)可能锁表太久,影响线上
迁移脚本如何安全中断与续跑
网络抖动、目标库抖动、OOM 都可能导致迁移中途失败。没有断点续传机制,只能重头来过,成本极高。
实操建议:
- 维护一张
migration_checkpoint表,字段至少含:table_name、last_id、updated_at - 每次成功写入一批数据后,更新该 checkpoint(用
REPLACE INTO或INSERT ... ON DUPLICATE KEY UPDATE) - 脚本启动时先查 checkpoint,若存在则从
last_id继续,否则从 0 或最小 ID 开始 - checkpoint 更新必须和数据写入在同一事务中(或用强一致性存储如 etcd),否则可能漏记
max_allowed_packet 限制、PostgreSQL 的 work_mem 设置、字符集不一致导致的 invalid byte sequence 错误,这些往往只在百万级数据后才暴露。别省略预检查步骤。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











