Go不会自动升级依赖,所有变动均源于显式命令;默认按语义化版本选最新兼容版,需在go.mod中写死精确版本(如v1.9.3)并提交go.sum才能真正锁定。

go get -u 不会自动更新依赖,Go 本身从不“自动”做任何事——所有版本变动都源于你或 CI 脚本显式执行的命令。真正的问题是:默认行为下,go get 会按语义化版本规则选最新兼容版,而你没意识到它已悄悄升级了间接依赖。
为什么 go get 看起来像在“自动更新”
常见错觉来自这几个操作:
- 运行
go get github.com/sirupsen/logrus(不带版本)→ 工具链按^1.2.0类规则拉取满足要求的最新 minor/patch 版本,比如从v1.8.1升到v1.9.3 - 执行
go mod tidy→ 它会补全缺失的间接依赖,若远程已有新版且满足约束,就可能写入go.mod中带// indirect的行 - CI 中用了
go get -u或未锁定GOFLAGS→ 尤其在 Go 1.17+ 下,-mod=readonly默认启用,但若被覆盖,go get就能改go.mod
go.mod 里怎么写才算真正锁死
Go 模块不支持 ~1.2 或 ^1.2 这类范围语法,只认精确版本或伪版本。要锁死,必须让 go.mod 显式声明不可漂移的值:
- 直接写死语义化版本:
require github.com/sirupsen/logrus v1.9.3—— 这是最常用、最可靠的方式 - 用伪版本锁定 commit:
require github.com/sirupsen/logrus v0.0.0-20230510152458-6b72e3a001f6—— 时间戳 + commit hash,比 tag 更稳 - 避免写
+incompatible结尾的版本(如v1.8.0+incompatible),这种写法会让go list -m -u判断失准,且 proxy 可能不提供完整历史 - 如果某个间接依赖你特别在意(比如
golang.org/x/net),哪怕没直接 import,也手动加一行require进go.mod,再跑go mod tidy
如何阻止 go get -u 影响你的依赖树
go get -u 默认只升直接依赖,但加上 ./ 或 all 就会递归扫全图。更麻烦的是它跳过 replace 和 exclude 条目,还无视 go.sum 里的更高版哈希 —— 它根本不会降级。
立即学习“go语言免费学习笔记(深入)”;
- 永远不要裸跑
go get -u;需要更新时,明确指定目标:go get github.com/sirupsen/logrus@v1.9.3 - 想查哪些能升但不改代码?用
go list -m -u—— 它只读,输出干净,加-json还能脚本解析 - CI 流程中禁用所有会改
go.mod的命令;加检查:go mod tidy -v && git status --porcelain go.mod go.sum | grep -q '.' && exit 1 - 私有模块记得配
GOPRIVATE=git.example.com/*,否则go list -m -u会报unrecognized import path,不是 bug,是网络策略拦的
replace 和 exclude 是临时方案,不是锁版本
这两个指令写在 go.mod 里,但作用完全不同,且都不该长期留在生产配置中:
-
replace是覆盖源,比如replace github.com/sirupsen/logrus => ../local-fix—— 它让整个 module graph 都用本地路径,但下游无法复现,CI 构建会失败 -
exclude是拦截器,比如exclude github.com/gorilla/mux v1.8.0—— 它不改变依赖图,只在校验阶段报错,逼你手动处理冲突 - 二者都需提交进版本库,但
replace应仅用于调试;上线前必须删掉,换成真实 tag 或伪版本 - 用
replace锁 commit(如=> github.com/sirupsen/logrus v0.0.0-20230510152458-6b72e3a001f6)看似可行,但优先级高于require,且不传递给下游模块,容易埋坑
真正容易被忽略的是:Go 的“锁定”靠的是 go.mod + go.sum 共同生效,缺一不可。go.sum 不是日志文件,删了或没提交,不同机器拉的包内容就可能不一致 —— 即使 go.mod 写死了版本,校验和对不上,构建照样失败。


















