30个站批量改一个字段,你是一个个连Navicat手动改,还是写条命令3秒跑完?数据库批量执行的四种方案,效率从低到高
有个站长在群里吐槽:新上线了40个站,每个站都要跑一套初始化SQL脚本,建表、插入默认数据、创建索引,加起来每个站大概30个SQL文件。他花了整整两天,一个个连Navicat,选数据库,右键"运行SQL文件",等进度条跑完,再切下一个库。改到第23个的时候Navicat崩了,前面的操作还没记哪些站跑过哪些没跑,只能从头来。
这其实是一个很典型的多库批量操作场景。站群、SaaS多租户、微服务分库,只要管理的数据库超过5个,手动连客户端一个一个点就是纯粹的体力活。更危险的是人工操作容易出错——漏执行、重复执行、执行顺序搞错,每一样都可能导致线上数据不一致。这篇文章把数据库批量执行的方案从最基础到最自动化排一遍,每个方案的适用场景、优缺点和实际脚本都摆出来。
数据库批量执行 四种方案速览
| 1 | GUI工具批量功能 — Navicat批处理作业、DBeaver任务调度,适合偶尔操作、不想写代码的场景 |
| 2 | Shell脚本循环执行 — 一个for循环搞定多库,灵活度最高,Linux服务器上标配方案 |
| 3 | 数据库迁移工具 — Flyway / Liquibase,带版本管理,适合开发环境和持续部署 |
| 4 | 并发批量框架 — DataX / DBSyncer / 自定义Go脚本,数据量大、时效要求高的场景 |
一、GUI工具的批量执行功能,最多人用但最容易被误用
Navicat和DBeaver都内置了批量执行能力,但很多人不知道或者用错了。先说Navicat。
Navicat的"批处理作业"藏在"自动运行"菜单里。你可以创建一个批处理,把多个"运行SQL文件"任务拖进去,设置好每个任务对应的数据库连接。然后点"运行",Navicat会按顺序逐个执行。关键点是:默认情况下,Navicat的批处理作业遇到一个SQL报错就会停下来,后面的任务不再执行。所以如果你跑的是初始化脚本,一定要勾选"忽略SQL语句错误"选项,否则中间某个文件有个小语法问题,整个批处理就卡住了。
Navicat批处理的一个坑:它不支持自动选择多个.sql文件拖入。如果你把10个.sql文件拖到批处理窗口,只会导入第一个。正确做法是逐个添加"运行SQL文件"任务,或者用命令行把多个.sql文件合并成一个再导入。另外,Navicat的批处理只能手动触发,没有定时调度能力,不能设置"每天凌晨3点自动跑"。
DBeaver的"任务调度"功能比Navicat强一档。在DBeaver里,你可以创建一个Task,配置多个SQL脚本的执行顺序,并设置每个脚本对应的数据库连接。而且DBeaver支持导出任务配置为XML文件,可以复用。但DBeaver也有短板——它跨数据库执行的时候不支持并发,还是串行的。

什么时候用GUI批量就够了?
数据库不超过10个,偶尔执行一次批量操作(比如月初更新配置表),不想折腾命令行——这种情况GUI批量完全够用。但如果数据库超过20个,或者需要定时执行、需要在多台服务器上执行,就该往下看了。
二、Shell脚本循环执行,20行代码解决90%的批量场景
这是最灵活的方案,也是生产环境里用得最多的。核心思路:一个for循环遍历数据库列表,对每个库执行mysql命令,记录日志,遇到错误可以选择跳过或中断。下面是几个实用的脚本模板。
基础版:单SQL文件跑多个数据库
#!/bin/bash# 在所有站点数据库中执行同一个SQL文件MYSQL_USER="root"MYSQL_PASS="your_password"MYSQL_HOST="127.0.0.1"SQL_FILE="/home/sql/update_config.sql"LOG_FILE="/home/sql/batch_exec_$(date +%Y%m%d_%H%M%S).log"# 数据库列表,格式:每行一个库名DATABASES=("site_001_db""site_002_db""site_003_db"# ... 继续添加)SUCCESS=0FAIL=0for db in "${DATABASES[@]}"; doecho "[$(date '+%H:%M:%S')] 正在执行: $db ..." | tee -a "$LOG_FILE"mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" -h"$MYSQL_HOST" "$db" < "$SQL_FILE" 2>> "$LOG_FILE"if [ $? -eq 0 ]; thenecho " ✓ $db 执行成功" | tee -a "$LOG_FILE"((SUCCESS++))elseecho " ✗ $db 执行失败,请检查日志" | tee -a "$LOG_FILE"((FAIL++))fidoneecho "==============================" | tee -a "$LOG_FILE"echo "执行完成:成功 $SUCCESS 个,失败 $FAIL 个" | tee -a "$LOG_FILE"进阶版:多SQL文件按顺序跑 + 并发控制
#!/bin/bash# 多个SQL文件按顺序执行,支持并发(同时跑N个库)MYSQL_USER="root"MYSQL_PASS="your_password"MYSQL_HOST="127.0.0.1"SQL_DIR="/home/sql/init_scripts" # SQL文件目录MAX_PARALLEL=5 # 最大并发数# SQL文件列表(按执行顺序排列)SQL_FILES=("01_create_tables.sql""02_add_indexes.sql""03_insert_defaults.sql""04_create_views.sql")DATABASES=($(mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" -h"$MYSQL_HOST" -e "SHOW DATABASES LIKE 'site_%';" -sN))exec_sql_for_db() {local db=$1local log="/tmp/batch_${db}.log"echo "[$(date '+%H:%M:%S')] 开始执行 $db" > "$log"for sql_file in "${SQL_FILES[@]}"; domysql -u"$MYSQL_USER" -p"$MYSQL_PASS" -h"$MYSQL_HOST" "$db" < "$SQL_DIR/$sql_file" 2>> "$log"if [ $? -ne 0 ]; thenecho " ✗ $sql_file 失败" >> "$log"return 1fidoneecho " ✓ $db 全部完成" >> "$log"}# 并发控制:后台执行,用wait控制并发数running=0for db in "${DATABASES[@]}"; doexec_sql_for_db "$db" &((running++))if [ $running -ge $MAX_PARALLEL ]; thenwait -n # 等任意一个完成((running--))fidonewait # 等所有后台任务完成echo "所有数据库执行完毕"Shell脚本批量执行的三个坑
1. 密码明文问题:不要在脚本里直接写密码。用 mysql_config_editor 设置免密登录,或者把密码放在 ~/.my.cnf 里设600权限。
2. 超时断开:大SQL文件执行时间长了mysql连接会断开。在执行前先设 SET SESSION wait_timeout=86400;。
3. 并发不要太高:同时跑太多库会打满数据库连接数和磁盘IO。一般建议并发数不超过CPU核数的2倍,单个SQL文件超过100MB就不要并发了。
三、Flyway和Liquibase,不只是批量执行,更是一套版本管理体系
如果批量执行SQL是你的日常操作——比如每天都有表结构变更、每周都有新站初始化——那Shell脚本会逐渐暴露出一个问题:没有版本追踪。你不知道每个库当前跑到了哪个版本的SQL,不知道哪些库漏执行了某个脚本,回滚也基本靠手工。
Flyway和Liquibase就是解决这个问题的。它们都是数据库迁移(Migration)工具,核心思路是:把每次数据库变更记录为一个版本,工具自动追踪每个库当前版本,只执行还没跑过的脚本。
| 对比维度 | Flyway | Liquibase |
|---|---|---|
| 脚本格式 | 纯SQL文件(V1__xxx.sql) | XML / YAML / JSON / SQL 都支持 |
| 版本追踪表 | 自动创建 flyway_schema_history | 自动创建 DATABASECHANGELOG |
| 回滚支持 | 社区版不支持,企业版支持 | 原生支持rollback |
| 学习成本 | 低,会写SQL就会用 | 中等,需要学ChangeSet语法 |
| 多数据库支持 | 25+ 种数据库 | 30+ 种数据库 |
| 站群场景适配 | 用命令行 + Shell循环包装 | 用contexts区分不同站点的变更 |
站群场景下Flyway怎么用:每个站点一个独立数据库,用Shell脚本在外面包一层循环,对每个库执行flyway migrate命令。Flyway会自动检查该库的flyway_schema_history表,只跑没执行过的版本。这样你不需要记住"这个库跑到了V3还是V5",Flyway帮你记。
Flyway + Shell 批量迁移示例
#!/bin/bashFLYWAY_HOME="/opt/flyway"for db in site_001 site_002 site_003; do$FLYWAY_HOME/flyway \-url="jdbc:mysql://localhost:3306/${db}_db" \-user=root -password=xxx \-locations="filesystem:/home/sql/migrations" \migratedoneLiquibase更适合复杂场景。如果你的站群不是每个站结构完全一样——比如A类站点多几个表、B类站点少几个字段——Liquibase的contexts和labels机制可以按标签区分哪些变更应用到哪些库,比Flyway更灵活。但代价是学习成本高,ChangeSet的XML写起来比纯SQL繁琐。

四、并发批量框架,当数据量和时效都上来的时候
Shell脚本的for循环并发有个天花板:它是进程级的并发,每个mysql命令fork一个进程,进程数多了开销很大。如果你有上百个库需要同时执行,或者单个SQL文件几百MB,Shell脚本就开始力不从心了。这时候要用专门的批量执行框架。
DataX(阿里开源)
专为数据同步设计的框架,支持MySQL、Oracle、PostgreSQL等十几种数据源之间的批量读写。优势是支持多Channel并发,一个大表可以切成N个分片同时读写,速度比单线程快5-10倍。适合数据迁移、多库同步场景。
DBSyncer(开源中间件)
实时数据同步中间件,支持全量+增量同步。特点是自带监控面板,能看到每个同步任务的实时速率、延迟、错误数。还支持自定义插件做数据转换。适合需要持续同步而不是一次性执行的场景。
自写Go脚本
如果你对性能有极致要求,用Go写一个批量执行器是最优解。goroutine天然适合并发,一个goroutine对应一个数据库连接,几百个库同时跑毫无压力。加上连接池管理和错误重试逻辑,50行代码就能写出比Shell强一个数量级的批量工具。
这里给一个Go语言批量执行的极简示例,30个数据库同时跑,每个库一个goroutine:
package mainimport ("database/sql""fmt""sync"_ "github.com/go-sql-driver/mysql")func main() {dbs := []string{"site_001", "site_002", /* ... site_030 */}sqlContent, _ := os.ReadFile("/home/sql/update.sql")var wg sync.WaitGroupsem := make(chan struct{}, 10) // 最多10个并发连接for _, db := range dbs {wg.Add(1)go func(dbName string) {defer wg.Done()sem <- struct{}{} // 获取信号量defer func() { <-sem }() // 释放信号量dsn := fmt.Sprintf("root:pass@tcp(127.0.0.1:3306)/%s", dbName)conn, _ := sql.Open("mysql", dsn)defer conn.Close()_, err := conn.Exec(string(sqlContent))if err != nil {fmt.Printf("✗ %s: %v\n", dbName, err)return}fmt.Printf("✓ %s 执行成功\n", dbName)}(db + "_db")}wg.Wait()fmt.Println("全部执行完毕")}五、四种方案怎么选,一张图说清楚
| 你的情况 | 推荐方案 | 理由 |
|---|---|---|
| 不到10个库,一个月操作一两次 | Navicat / DBeaver 批处理 | 不折腾,点几下鼠标就完事 |
| 10-50个库,每周都有批量操作 | Shell脚本 + for循环 | 灵活、免费、几乎所有Linux都自带 |
| 需要版本管理,多人协作开发 | Flyway / Liquibase | 自动追踪版本,不会重复执行或遗漏 |
| 50+个库,数据量大,时效要求高 | Go脚本 / DataX / DBSyncer | 并发能力、错误处理、监控都比Shell强 |
| 站群,每个站独立库,经常批量改 | Shell脚本(日常)+ Flyway(版本) | Shell处理灵活操作,Flyway管理结构化变更 |
还有一个实操细节:不管用哪种方案,先在一个测试库上跑一遍。很多人就是跳过了这一步,直接把脚本扔到所有库上跑,结果SQL里有个语法错误,30个库全挂了。批量执行出错的时候,影响面是"所有库",不是"一个库"。
六、站群场景的特殊需求:不只是批量执行,是批量管理
站群的数据库批量执行有个特殊需求:不同站点可能需要执行不同的SQL。比如站点A、B、C刚上线需要全量初始化,站点D、E、F只需要更新一个配置表。这不是简单的"所有库跑同一个SQL",而是需要按站点分组、按标签过滤。
方案A:Shell脚本分组
在脚本里维护一个配置文件,把数据库名和所属分组对应起来。执行时根据分组选择不同的SQL目录。简单但维护成本随站点数增长。
方案B:数据库元信息表
每个站点的库里放一张site_meta表,记录站点类型、版本号、最后更新SQL时间。批量脚本先读meta表决定执行哪些SQL。自动化程度高,但初始设计需要一次投入。
方案C:系统化管理
用UC建站系统的内容中台统一管理多站数据库。系统层面维护站点分组和SQL版本状态,批量执行时自动匹配,不需要手工维护脚本里的站点列表。适合站点数超过50个的规模化场景。
不管是哪种方案,核心原则是一样的:批量执行的可追溯性比执行速度更重要。你至少要能回答三个问题:哪些库已经跑了?哪些库跑失败了?失败的原因是什么?如果这三个问题回答不了,批量执行就不是提效工具,是埋雷工具。
最后说一句:数据库批量执行这件事,工具选择不是最难的,最难的是养成"先在测试库验证、记录执行日志、保留回滚方案"这三个习惯。Shell脚本也好,Flyway也好,DataX也好,没有这三个习惯兜底,早晚会出一次让你后悔没做备份的事故。
