在使用 go mod 管理依赖时,go.mod 往往是排查版本问题、理解项目结构的第一入口。但不少人执行一次 go mod tidy 之后,会发现文件里突然出现两段 require,看起来像是被“拆开”了。其实这不是异常,而是 Go 对依赖关系做了更清晰的分层展示。看懂这两段的含义后,你就能更快判断哪些包是项目主动使用的,哪些只是被上游依赖带进来的。
为什么 go.mod 会出现两段 require
在 Go 项目中,go.mod 是 go mod 的核心配置文件。常见配置项通常包括 module、go 和 require。一个经过 go mod tidy 整理后的文件,常见结构类似下面这样:
module github.com/project/demo
go 1.24.0
require (
github.com/gin-gonic/gin v1.10.1
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.30.1
)
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
... // 省略其他依赖
)
这两段 require 并不是重复声明,而是 Go 按依赖来源做的一次分类:前一段是项目直接使用的依赖,后一段是依赖链条中传递进来的间接依赖。这样做的好处是,开发者打开文件时能先看清项目真正“主动依赖”了什么,再去判断其余依赖是如何被带入的。
第一段:项目直接依赖
第一段 require 表示直接依赖,也就是项目源码里通过 import 明确引用到的模块。这部分通常最值得关注,因为它直接反映了项目选用了哪些框架、数据库驱动或工具库。
require (
github.com/gin-gonic/gin v1.10.1
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.30.1
)
例如上面这段里,github.com/gin-gonic/gin、gorm.io/driver/sqlite 和 gorm.io/gorm 都属于业务代码直接使用到的模块。平时阅读一个项目的技术栈,通常先看这里就够了,因为这部分最能说明项目本身依赖了哪些能力。
第二段:带 // indirect 的间接依赖
第二段 require 列的是间接依赖,特征也很明显:每一项后面都带有 // indirect 标记。它的意思不是“没用”,而是“项目没有直接 import 这个模块,但某个直接依赖需要它”。

require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
... // 省略其他依赖
)
这类依赖本质上是依赖树的一部分。你的代码没有直接引用它们,但项目要能稳定构建、测试和运行,仍然需要这些模块及其固定版本。项目规模越大,依赖链越深,第二段通常就越长。
因此,看到大量 // indirect 并不代表 go.mod 混乱,更多时候只是说明项目引入的上游库本身依赖比较丰富。它们被明确记录下来,反而更有利于追踪版本来源和排查兼容性问题。
Go 1.17 之后,为什么这种结构更常见
自 Go 1.17 之后,执行 go mod tidy 时,工具会更明确地区分直接依赖和间接依赖,并把项目涉及到的间接依赖也列出来。于是,两段式 require 就成了很多项目里的常见形态。

这么做的价值在于版本可重复性。虽然它不等同于 npm 或 composer 中的 .lock 文件,但思路有相似之处:尽量把依赖关系和版本边界表达清楚,减少未来升级时出现“不知道为什么变了”的情况。对团队协作、CI 构建和长期维护来说,这一点很重要。
require 不一定只能分成两段
两段式是常见结果,但并不是唯一合法写法。go.mod 中的 require 可以按需要自行分组,因此你也可能看到三段式甚至更多分段。
有些项目会单独拉出一段,专门声明需要手动锁定版本的间接依赖。比如某个上游模块升级后带来了兼容问题,这时就可以显式指定某个间接依赖的版本,以确保构建结果稳定。
这种写法并不违背 Go 的依赖管理规则。即便是间接依赖,只要你在 go.mod 中显式写出,并带上 // indirect 标记,通常也不会被 go mod tidy 轻易清掉。换句话说,go mod 在默认自动整理之外,也保留了足够的人工控制空间。
实际阅读 go.mod 时该怎么看
如果你的目标是快速理解项目依赖,最直接的办法是先看第一段 require,因为那里列出的就是项目真正直接使用的模块。只有在排查构建异常、版本冲突或某个包为何被引入时,再去看第二段以及对应的 // indirect 项。
从这个角度看,go.mod 里出现两段 require 不是多余设计,而是为了把“主动依赖”和“传递依赖”分开显示。它让文件更可读,也让版本控制更稳定。随着 Go 生态不断成熟,go mod 的这套机制已经越来越接近“简单但够用,而且遇到复杂场景也能手动干预”的状态。













