Bootstrap 是什么
TiDB 的 Bootstrap 是启动时的初始化过程,由 session.go 中的 BootstrapSession 完成。它在 tidb-server 的 createStoreAndDomain 中被调用。
先判断是新建还是升级
Bootstrap 分为两种情形。它首先会去存储层中读取 BootstrapKey,取得当前的 bootstrapVersion:
- 如果版本号为 0,说明这是一个新集群,执行 bootstrap;
- 如果版本号低于
currentBootstrapVersion,说明是已有集群需要升级,执行 upgrade。
func BootstrapSession(store kv.Storage) (*domain.Domain, error) {
ver := getStoreBootstrapVersion(store)
if ver == notBootstrapped {
runInBootstrapSession(store, bootstrap)
} else if ver < currentBootstrapVersion {
runInBootstrapSession(store, upgrade)
}
// ...
}
bootstrap:创建系统表并写入元数据
新集群的初始化会先做一次已初始化检查,然后分两步完成:
// bootstrap initiates system DB for a store.
func bootstrap(s Session) {
b, err := checkBootstrapped(s)
if err != nil {
log.Fatal(err)
}
if b {
upgrade(s)
return
}
doDDLWorks(s) // 创建一些系统表
doDMLWorks(s) // 插入一些元数据到系统表
}
doDDLWorks 负责建立系统库与系统表;doDMLWorks 负责写入初始元数据。这个划分很清晰:结构走 DDL,数据走 DML。
一个具体的例子是系统变量。所有系统变量定义在 sysvar.go 的 defaultSysVars 中,在 init 阶段被设置到 SysVars map 里;而在 doDMLWorks 时,它们会被逐一插入到 mysql.GLOBAL_VARIABLES 表中。
upgrade:按版本号逐级升级
升级路径则依据版本号逐级推进:
func upgrade(s Session) {
ver, err := getBootstrapVersion(s)
if err != nil {
log.Fatal(errors.Trace(err))
}
if ver >= currentBootstrapVersion {
// 已被更高版本的 TiDB server 升级过
return
}
// ...
if ver < version15 {
upgradeToVer15(s) // 例如这里创建了 mysql.gc_delete_range
}
// ...
}
可以看到,每个版本区间对应一个升级函数。这样做的好处是升级逻辑可以累积,任意旧版本都能通过顺序执行升级函数到达最新状态。已是最新版本时直接返回,避免重复执行升级动作。
启动的后台协程
除了元数据初始化,BootstrapSession 还负责启动若干长期运行的后台任务。
LoadPrivilegeLoop:权限变更监听
因为 TiDB 是分布式的,权限数据可能被其他 TiDB 实例修改了。为了让所有实例保持一致,TiDB 使用 etcd 作为变更通知通道:当权限被修改时,通知所有 TiDB 实例重新加载新的权限数据。
其职责包括创建一个 goroutine 循环加载权限表,并在启动时先做一次全量更新。从注释可以看出,它应当在 BootstrapSession 中只被调用一次。
UpdateTableStatsLoop:统计信息维护
这个循环用于做 Analyze。它会创建统计信息句柄、注册 DDL 事件通道(表结构变化时统计信息需要相应更新),并先执行一次更新。若统计信息租约为非正值则直接返回;否则会启动统计信息更新 worker;在开启自动分析的情况下,还会额外启动自动分析 worker。
统计信息的重要性在于:优化器选择执行计划时依赖统计信息估算行数,统计信息陈旧会直接导致执行计划选错。
GCWorker:垃圾回收
GCWorker 同样在 BootstrapSession 中被启动,且仅在存储后端支持 etcd 时启动:
if raw, ok := store.(domain.EtcdBackend); ok {
err = raw.StartGCWorker()
if err != nil {
return nil, errors.Trace(err)
}
}
GCWorker 主要做三件事:
- 处理 DDL 的 DropTable、DropIndex 等产生的 Delete range 操作;
- 执行存储层的 GC,基于数据生命周期清理保存的历史版本数据;
- 扫描全库,查找并清理过期的锁。
小结
Bootstrap 这条链路虽然叫「初始化」,但它实际承担了三类职责:元数据结构与初始数据的一次性建立、版本化的升级推进、以及长期后台任务的启动。理解这条链路,对排查启动失败、升级异常和后台任务不生效等问题都很有帮助。
| 版本判定 | 读取 BootstrapKey 取得版本号,为 0 则初始化新集群,低于当前版本则升级 |
|---|---|
| DDL 工作 | doDDLWorks 创建系统库与系统表 |
| DML 工作 | doDMLWorks 写入初始元数据,例如把默认系统变量插入系统表 |
| 升级机制 | 按版本区间顺序执行升级函数,已是最新版本直接返回 |
| 后台任务 | 权限加载循环、统计信息更新与自动分析、GCWorker 垃圾回收 |