golang-migrate

Published on:

migrate 是一个用go写的数据库迁移工具,提供CLI和导入库两种方式,支持的数据库有MySQL,PostgreSQL,SQLite,MongoDB等。

使用migrate数据库迁移工具可以在更新的时候避免一些因手动操作导致的意外事故,提高更新流程的集成度。

migrate CLI#

安装#

MacOS或者Windows可以通过软件包下载

MacOS

1
brew install golang-migrate

Windows

1
scoop install migrate

Linux从这里 Release Downloads 下载工具包,解压即可使用。

常用的迁移命令#

创建sql文件#

1
migrate create -ext sql -dir ./sit106/sql -seq create_test_table

create命令会创建两个空sql文件,一个名字带up,表示升级文件,一个名字带down,表示回滚文件,文件名带有顺序的版本号和指定的文件名。

升级 N 个版本#

1
migrate -path ./sit106/sql -database 'mysql://root:123456@tcp(127.0.0.1:3306)/test?query' up [N]
1
2
3
4
5
6
## 第一次执行up命令后,会自动创建 schema_migrations 表,记录迁移版本号和状态。
CREATE TABLE `schema_migrations` (
`version` bigint(20) NOT NULL,
`dirty` tinyint(1) NOT NULL,
PRIMARY KEY (`version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

每一次执行迁移命令,会首先从 schema_migrations 表获取版本号,再从指定目录获取比当前版本号要高的迁移的文件,再执行迁移。

注意: 不加参数 N ,则更新到最新,默认不加。

降级 N 个版本#

1
migrate -path ./sit106/sql -database 'mysql://root:123456@tcp(127.0.0.1:3306)/test?query' down [N]

注意: 不加参数 N ,则回滚到版本0。

忽略第 N 版本的脏状态 dirty state#

1
migrate -path ./sit106/sql -database 'mysql://root:123456@tcp(127.0.0.1:3306)/test?query' force N

当升级或者回滚的sql脚本出现错误,当前版本会被标记为脏状态,无法继续更新或者回滚。
在修正sql脚本的错误后,需要使用次命令,清除脏状态,方可以继续升级或者回滚。

查看当前迁移版本#

1
migrate -path ./sit106/sql -database 'mysql://root:123456@tcp(127.0.0.1:3306)/test?query' version

跳跃到第 N 个版本#

1
migrate -database 'mysql://root:123456@tcp(127.0.0.1:3306)/test?query' goto N

按顺序升级或者回滚到指定版本。

更多用法通过帮助获取#

1
migrate

以上命令的 path 参数的写法
-path ./sit106/sql
是一个简写的方式,原型写法为

1
2
3
4
5
6
7
8
9
10
仅当从文件系统加载配置的时候,可以使用简写方式。

除了支持从file读取,还支持从 github,gitlab,bitbucket 等远程读取,具体访问 [source](https://github.com/golang-migrate/migrate/tree/master/source) 查看。


## migrate library

### 安装
```bash
go get github.com/golang-migrate/migrate

example migration.go#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package migration

import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"github.com/golang-migrate/migrate"
"github.com/golang-migrate/migrate/database/mysql"
_ "github.com/golang-migrate/migrate/source/file"
"log"
)

func init() {
log.Println("start migrate")
db, _ := sql.Open("mysql", "root:1234@tcp(localhost:3306)/test?multiStatements=true")
driver, err := mysql.WithInstance(db, &mysql.Config{})
if err != nil {
log.Fatal(err)
}
m, err := migrate.NewWithDatabaseInstance(
"file://publish/sit106/sql",
"mysql",
driver,
)
if err != nil {
log.Fatal(err)
}
err = m.Up()
if err != nil {
m.Down(1)
log.Fatal(err)
}
log.Println("end migrate")
}

从以上代码可以看出,当up命令发生错误时,需要执行down命令,但是具体down几个版本,则需要自己控制参数。

扩展建议#

可新增一个字段pre_version,记录上一个版本号。

1
2
3
4
5
6
CREATE TABLE `schema_migrations` (
`version` bigint(20) NOT NULL,
`pre_version` bigint(20) NOT NULL,
`dirty` tinyint(1) NOT NULL,
PRIMARY KEY (`version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

当执行up命令前,记录当前的版本号version,执行up命令后,得到new_version,最后更新schema_migrations表。

1
set pre_version=version,version=new_version

当执行down命令时,首先取pre_version的值,回滚版本后,更新后执行。

1
set version=pre_version

这样至少可以回滚到上一个版本。

需要注意的事#

冲突#

在一个多人开发的项目中,难免会遇到冲突,所以在代码审查的时候要尤其注意。

幂等#

尽量让你的迁移时幂等的——连续两次运行相同的sql语句得到相同的结果,这会让迁移代码更加健壮。例如使用
CREATE TABLE IF NOT EXISTS
代替
CREATE TABLE

事务#

如果一次迁移中有多个命令/查询,最好把它们包在一个事务中,这样,当其中一个命令失败,我们的数据库将保持不变。