大SQL文件不能直接用db.Exec执行,因MySQL默认max_allowed_packet仅4MB且database/sql驱动不支持多语句;文件中混有注释、引号内分号、DELIMITER等,需用TiDB解析器等准确切分语句,再分批执行。

大SQL文件为什么不能直接用db.Exec执行
因为MySQL的max_allowed_packet限制(默认4MB),以及Go的database/sql驱动对单条语句的解析边界不支持多语句拼接(如INSERT INTO t VALUES(1); INSERT INTO t VALUES(2);会被视为非法)。更关键的是,大SQL文件里混着CREATE TABLE、INSERT、注释、空行、DELIMITER切换——直接按行切或按分号切都会在字符串字面量、注释内误断。
用mysqlclient命令行导入最稳,但得绕过Go进程控制
多数线上环境不允许exec.Command("mysql", ...)调用外部二进制(权限/路径/版本不可控),但如果能确认目标机器已装mysql且可访问,这是最省心的方案:
mysql -h 127.0.0.1 -P 3306 -u root -p'xxx' db_name < /path/to/backup.sql
注意三点:
-
mysql客户端默认启用--auto-reconnect,但超时后不会重试失败语句,需加--force跳过错误继续 - 密码写在命令行会泄露到
ps aux,应改用~/.my.cnf配置文件([client] user=root password=xxx)并设chmod 600 - 大文件导入时加
--default-character-set=utf8mb4,避免建表语句因字符集不一致报错
纯Go解析必须识别SQL语句真实边界
不能简单按;分割,要模拟MySQL客户端的分句逻辑:跳过单双引号内、跳过--和/*...*/注释内的分号,还要处理DELIMITER $$临时改分隔符的情况。推荐用现成库github.com/skeema/tengo或轻量方案github.com/pingcap/parser(TiDB解析器,稳定且支持MySQL全语法):
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
用parser.New()逐token解析比正则可靠得多,示例关键逻辑:
ast, err := parser.Parse(sqlBytes, "", "")
if err != nil {
// 跳过解析失败的语句,记录位置供人工检查
}
for _, stmt := range ast {
if _, ok := stmt.(*ast.CreateTableStmt); ok {
// 建表语句单独执行,避免与后续INSERT混批
db.Exec(stmt.Text())
}
}
注意:parser返回的是AST节点,stmt.Text()才是还原后的原始SQL字符串(含换行缩进),别用fmt.Sprintf拼接。
分批执行时INSERT要合并但不能超max_allowed_packet
单条INSERT带几千行值是高效做法,但总长度受MySQL服务端限制。实操中建议:
- 每批INSERT控制在500–1000行,按
len(insertSQL)动态累加,超80%max_allowed_packet(如3MB)就切批 - 不要把
CREATE INDEX和INSERT混在同一批——索引会拖慢写入,应先批量INSERT,最后统一建索引 - 事务粒度按“一张表”划分:每张表的全部INSERT包在一个
tx里,避免跨表长事务锁表 - 遇到
ERROR 1205 (HY000): Deadlock found要重试,但别无脑重试——先SELECT COUNT(*)确认数据是否已存在
真正难的不是切分,是当SQL里有LOAD DATA INFILE或CREATE PROCEDURE这类非标准语句时,解析器可能不覆盖——得提前扫描文件头,遇到这类语句直接交给mysql命令行执行。

















