go mod tidy 只删除未被 import 语句显式引用且非间接依赖的模块,不识别注释、构建约束、测试文件(除非启用对应环境)、运行时加载等“看不见”的依赖。

go mod tidy 为什么删不掉那些“看不见”的包
它只认 import 语句,不认注释、构建约束、测试文件(除非你当前环境启用它们)、运行时加载路径。比如你在 //go:generate 里写了 go run github.com/xxx/cmd,但没加 // +build generate,go mod tidy 就当它不存在,直接把对应模块删掉——而你下次跑 go generate 就会报错。
常见漏判场景:
-
import _ "database/sql"或import _ "github.com/lib/pq"这类空白导入会被保留,但如果你删了某处 init 注册,又忘了删这个_导入,tidy仍会留着它 -
//go:build integration文件里的依赖,在默认构建环境下不会被扫描,tidy会误删 - 测试文件(
*_test.go)中的import只有在go test ./能跑通的前提下才被tidy纳入——如果测试本身因其他原因失败,tidy可能跳过整批测试依赖 -
replace指向本地路径的模块,若该路径已删或重命名,tidy不报错也不清理,只是让后续go build失败
怎么确认一个包到底是不是“历史残留”
别靠肉眼扫 go.mod,用命令交叉验证:
- 查谁在用它:
go mod why -m github.com/some/oldpkg—— 如果输出# github.com/some/oldpkg下面全是(main)或(unknown),基本就是残留 - 看实际依赖树:
go list -deps -f '{{if not .Standard}}{{.ImportPath}}{{end}}' ./ | sort -u,对比go list -m all | grep oldpkg是否出现在结果里 - 检查是否仅存于
// indirect行:go mod graph | grep oldpkg如果无输出,说明没人真正引用它 - 临时屏蔽再验证:在
go.mod里用//注释掉那行require,然后跑go build ./和go test ./—— 如果都过,大概率可删
清理后 vendor 目录为什么还有旧包
go mod tidy 完全不碰 vendor/。它只改 go.mod 和 go.sum。所以即使你 tidy 干净了,vendor/ 里还躺着上古版本的包。
要同步 vendor:
- 先删旧 vendor:
rm -rf vendor/ - 再重新生成:
go mod vendor - 注意:
go mod vendor默认包含所有go.mod里的依赖(含// indirect),不会自动剔除“编译时未用到”的包——它只保证可构建,不保证精简 - 如果想进一步压缩 vendor,得配合构建标记:比如
GOOS=linux GOARCH=amd64 go mod vendor,避免把 js/wasm 构建专用包也塞进去
go clean -modcache 能不能代替 tidy
不能。它俩干的事完全不同:go clean -modcache 是清本地磁盘缓存,不影响 go.mod;go mod tidy 是修正依赖声明,不删磁盘文件。
什么时候该用 go clean -modcache:
-
go mod tidy报invalid version: unknown revision,但远程 tag 明明存在 -
go list -m all显示的版本和go.mod对不上 - 私有代理返回 checksum mismatch,且
GOSUMDB没关
但清完 cache 后,你依然得跑一遍 go mod tidy,否则 go.mod 里那些“历史残留”还在。
最易被忽略的点:清理前没确认所有 .go 文件已保存,尤其是刚删掉 import 的文件——tidy 扫的是磁盘上的内容,不是编辑器缓冲区。











