
go 程序中启动 goroutine 执行数据库迁移任务时,若未等待其完成便退出 main 函数,会导致所有并发任务被强制终止——这是因 go 运行时仅等待 main 协程结束,而非所有协程。解决方法是使用 sync.waitgroup 同步协调 goroutine 生命周期。
go 程序中启动 goroutine 执行数据库迁移任务时,若未等待其完成便退出 main 函数,会导致所有并发任务被强制终止——这是因 go 运行时仅等待 main 协程结束,而非所有协程。解决方法是使用 sync.waitgroup 同步协调 goroutine 生命周期。
你的代码问题核心在于:main 函数在启动所有 go processBatch(offset) 后立即执行 legacyDB.Close() 和 v2DB.Close(),随后函数返回,整个程序退出。此时,尚未开始或正在执行的 goroutine 会被无提示终止(Go 不会等待非主 goroutine 完成),因此 processBatch 中的日志、查询、数据写入等逻辑根本不会执行——这正是你观察到“脚本瞬间结束”“无输出”“goroutine 数量不固定”的根本原因。
要修复该问题,必须让 main 显式等待所有迁移 goroutine 完成。推荐使用 sync.WaitGroup,它专为这类“等待 N 个并发操作结束”的场景设计:
import "sync"
// 在 main 函数中替换原有循环部分:
fmt.Println("Total: " + strconv.Itoa(total))
fmt.Println("Loops: " + strconv.Itoa(loops))
var wg sync.WaitGroup
wg.Add(loops) // 声明需等待的 goroutine 总数
for i := 0; i < loops; i++ {
offset := i * batchsize
// 注意:必须将 offset 作为参数传入闭包,避免循环变量捕获问题
go func(o int) {
defer wg.Done() // 每个 goroutine 结束时调用
processBatch(o)
}(offset)
}
wg.Wait() // 阻塞直到所有 goroutine 调用 Done()
// 此时才安全关闭数据库连接
legacyDB.Close()
v2DB.Close()⚠️ 关键注意事项:
-
闭包变量捕获陷阱:原代码中若直接在 goroutine 内引用
offset(如go processBatch(offset)),由于for循环快速迭代,所有 goroutine 可能共享同一个offset变量地址,最终都拿到最后一次循环的值。务必通过函数参数显式传递当前值(如func(o int))。 -
不要在 goroutine 外提前关闭 DB:
defer legacyDB.Close()在main开头声明,会在main返回时立即触发——这会中断所有活跃连接。应移至wg.Wait()之后手动调用。 -
错误处理需在 goroutine 内部进行:
processBatch中的panic(err)仅会终止当前 goroutine,不影响其他任务。生产环境建议改用日志记录 + 原子计数器统计失败批次,避免单点失败导致整体中断。 -
连接池与并发安全:
*sql.DB本身是并发安全的,可被多个 goroutine 同时使用。但需确保legacyDB和v2DB的SetMaxOpenConns和SetMaxIdleConns配置合理(例如设为loops或略高),否则可能因连接耗尽而阻塞或超时。
总结:Go 的并发模型强调“明确同步”,没有隐式等待机制。任何依赖 goroutine 完成的任务,都必须通过 WaitGroup、channel 或 context 显式协调生命周期。忽略这一点,是并发迁移类脚本最常见的静默失败原因。

















