Data Migration: golang-migrate/migrate

 28th December 2020 at 11:55am

golang-migrate/migrate 是一款用 Go 写的 DB migration 工具。虽然它是 Go 语言编写,但它的使用不跟语言和框架绑定。

参考 官方文档 进行安装。AUR 也有 。2020 年 12 月 27 日使用的版本是 4.12.2,后面可能用更新的版本。

这个工具不做冲突上的处理。如果多人协作,多个人提交了同一版本号的 migration 文件,工具无法进行处理。

常用工作流

在初始化本地开发环境中,需要做:

  1. 初始化开发数据库(下面描述)
  2. 向上执行全部数据变更(下面描述)

新增一套 migration SQL:

# Format:
#   migrate create -ext sql -dir . <describe_your_change>
# Example:
migrate create -ext sql -dir . create_note_table

向上或向下执行数据变更:

# 向上
## 执行全部
migrate -database `cat database_url` -path . up
## 执行 2 份向上变更(即以当前数据的版本为基准,做更新的变更)
migrate -database `cat database_url` -path . up 2

# 向下
## 执行全部
migrate -database `cat database_url` -path . down
## 执行 2 份向下变更(即以当前数据的版本为基准,回滚两次变更)
migrate -database `cat database_url` -path . up 2

初始化开发数据库

migrate 不参与数据库的创建和销毁。用这里的 脚本 去做。

将数据库的 connection url 写入 database_url 文件中,注意下面的命令中密码是要被替换的,而且需要是 URL encode 过的:

echo "postgres://squirrel_admin:<password_url_encoded>@localhost/squirrel?sslmode=disable&search_path=public" > database_url

需要指定 search_path 的原因是(来源):

When the schema and role names are the same, you might run into issues if you create this schema using migrations. This is caused by the fact that the default search_path is “$user”, public. In the first run (with an empty database) the migrate table is created in public. When the migrations create the $user schema, the next run will store (a new) migrate table in this schema (due to order of schemas in search_path) and tries to apply all migrations again (most likely failing).

可以使用 Python 来做 URL encode:

$ python3 -c 'import urllib.parse; print(urllib.parse.quote(input("String to encode: "), ""))'
# or
$ python2 -c 'import urllib; print urllib.quote(raw_input("String to encode: "), "")' 

migrate 过程出错时

当 migrate 过程失败时,工具会在数据库中标记某个版本失败(dirty)了。你重试 migrate 时,会报这种错:

Dirty database version 1. Fix and force version

这种情况下需要你人工确认下数据的状态,有必要的话做一些修复,然后执行:

migrate -database `cat database_url` -path . force VERSION

注意:force 后的版本,是你认为数据目前在这个版本(即做过这个版本及之前所有的 up 脚本),意味着:

  • 如果从版本 4 up 到 5 过程失败了,即执行第 5 的 up.sql 失败,那么:
    • 你修改了数据,使得数据库已经反映出 5 up 的结果了,此时应该 force 5;
    • 如果修改是某种回滚,数据回到做完 4 up 之后的状态,那么你应该 force 4,并再执行一次 up
  • 如果从版本 5 down 到 4 过程失败了,即执行 5 的 down.sql 失败,那么:
    • 你修改了数据,使得数据库已经反映出 5 down 的结果了,此时应该 force 4;
    • 如果修改是某种回滚,数据回到做完 5 up 之后的状态,那么你应该 force 5,并再执行一次 down

参考