1 项目地址
https://github.com/golang-migrate/migrate
2 使用情景
一些项目功能会涉及到数据库和代码逻辑的修改,对于数据库的修改,虽然 gorm 之类的工具能够在代码里面适配大部分情况,但是不能覆盖所有数据库变更情况,而且也不够清晰。为清楚表示某次代码提交设计的数据库的修改并且方便 devops 部署服务,可以使用 golang-migrate 这样的工具明确的标识对于数据的某次修改,可以对这些修改做部署和回滚。
使用 golang-migrate 有两种方式,一种是使用 migrate 提供的 CLI,一种是使用 golang library。本次测试使用 migrate CLI。
2.1 支持数据库类型
Source drivers: github-ee, godoc-vfs, s3, bitbucket, go-bindata, gcs, file, github, gitlab
Database drivers: cockroachdb, firebird, postgresql, redshift, clickhouse, postgres, cockroach, firebirdsql, mysql, crdb-postgres, mongodb, mongodb+srv, neo4j, pgx, spanner, sqlserver, stub, cassandra
3 使用方法
一开始使用 mysql 做测试,结果一直提示错误,就先用 github 上的教程用 postgres 测试了一遍。之后再解决 mysql 测试的问题。
3.1 安装 migrate CLI
3.1.1 参考
https://github.com/golang-migrate/migrate/tree/master/cmd/migrate
3.1.2 安装
直接 Release Downloads 下载对应版本即可。官网上的说明会让人误以为用 migrate CLI 的版本只支持某种数据库,但是实际测试发现下载的 migrate CLI 可以支持各种 Database drivers。
3.2 postgres 测试
3.2.1 参考
https://github.com/golang-migrate/migrate/blob/master/database/postgres/TUTORIAL.md
3.2.2 启动 postgres 数据库
在 192.168.10.212 这台测试服务器上用 docker 启动 postgres 数据库
docker run --name postgres -e POSTGRES_PASSWORD=mysecretpassword -d postgres:14.0
- 1
psql -h localhost -U postgres -w -c "create database example;"
3.2.3 创建某次数据库变更的 sql 文件
mkdir migrations #首次执行,用于存放变更的 sql 文件
- 1
比如说这次修改我们需要新增一个 users 的表,
migrate create -ext sql -dir ./migrations -seq create_users_table
- 1
000001_create_users_table.up.sql000001_create_users_table.down.sql000001_create_users_table.up.sql000001_create_users_table.down.sql
migrations
├── 000001_create_users_table.down.sql
├── 000001_create_users_table.up.sql
- 1
- 2
- 3
000001_create_users_table.up.sql
CREATE TABLE IF NOT EXISTS users(
user_id serial PRIMARY KEY,
username VARCHAR (50) UNIQUE NOT NULL,
password VARCHAR (50) NOT NULL,
email VARCHAR (300) UNIQUE NOT NULL
);
- 1
- 2
- 3
- 4
- 5
- 6
000001_create_users_table.down.sql
DROP TABLE IF EXISTS users;
- 1
3.2.4 应用某次数据库修改
migrate 的 up 子命令用来应用某一次数据库变更, down 子命令用来回滚数据库变更操作。
比如 up [N], N 表示执行多少个数据库变更任务,那具体是执行 ./migrations 下面的哪些 sql 脚本呢?
up [N] Apply all or N up migrations
down [N] [-all] Apply all or N down migrations
Use -all to apply all down migrations
- 1
- 2
- 3
000001_create_users_table.up.sql
3.2.4.1 首次执行 migrate up
migrate -database 'postgres://postgres:mysecretpassword@192.168.10.212:5432/example?sslmode=disable' -path ./migrations up 1
1/u create_users_table (47.755135ms)
- 1
- 2
执行 migrate up 后,数据库中增加了 users 表和 schema_migrations 表
example=# d
List of relations
Schema | Name | Type | Owner
--------+-------------------+----------+----------
public | schema_migrations | table | postgres
public | users | table | postgres
public | users_user_id_seq | sequence | postgres
(3 rows)
example=# d users;
Table "public.users"
Column | Type | Collation | Nullable | Default
----------+------------------------+-----------+----------+----------------------------------------
user_id | integer | | not null | nextval('users_user_id_seq'::regclass)
username | character varying(50) | | not null |
password | character varying(50) | | not null |
email | character varying(300) | | not null |
Indexes:
"users_pkey" PRIMARY KEY, btree (user_id)
"users_email_key" UNIQUE CONSTRAINT, btree (email)
"users_username_key" UNIQUE CONSTRAINT, btree (username)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
example=# select * from schema_migrations;
version | dirty
---------+-------
1 | f
(1 row)
- 1
- 2
- 3
- 4
- 5
3.2.4.2 回滚操作
migrate -database 'postgres://postgres:mysecretpassword@192.168.10.212:5432/example?sslmode=disable' -path ./migrations down
Are you sure you want to apply all down migrations? [y/N]
y
Applying all down migrations
1/d create_users_table (31.550358ms)
- 1
- 2
- 3
- 4
- 5
migrations down 后,数据库恢复到应用 create_users 之前的状态,users 表被删除,schema_migrations 表记录被删除
example=# select * from schema_migrations ;
version | dirty
---------+-------
(0 rows)
- 1
- 2
- 3
- 4
3.2.5 应用多次修改
- 为了测试 migrations up [N] 执行多次修改的情形,第二次修改我们使用事务为 users 表增加 COLUMN,
migrate create -ext sql -dir ./migrations -seq add_mood_to_users000002_add_mood_to_users.up.sql000002_add_mood_to_users.down.sql
000002_add_mood_to_users.up.sql
BEGIN;
CREATE TYPE enum_mood AS ENUM (
'happy',
'sad',
'neutral'
);
ALTER TABLE users ADD COLUMN IF NOT EXISTS mood enum_mood;
COMMIT;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
000002_add_mood_to_users.down.sql
BEGIN;
ALTER TABLE users DROP COLUMN IF EXISTS mood;
DROP TYPE enum_mood;
COMMIT;
- 1
- 2
- 3
- 4
- 5
- 6
- 第三次修改为 users 表增加 role_id 这个 COLUMN
migrate create -ext sql -dir ./migrations -seq add_roleid_to_users000003_add_roleid_to_users.up.sql000003_add_roleid_to_users.down.sql
000003_add_roleid_to_users.up.sql
ALTER TABLE users ADD COLUMN IF NOT EXISTS role_id INTEGER;
- 1
000003_add_roleid_to_users.down.sql
ALTER TABLE users DROP COLUMN IF EXISTS role_id;
- 1
- 这样 migrations 目录下有如下6个 sql 文件:
.
└── migrations
├── 000001_create_users_table.down.sql
├── 000001_create_users_table.up.sql
├── 000002_add_mood_to_users.down.sql
├── 000002_add_mood_to_users.up.sql
├── 000003_add_roleid_to_users.down.sql
└── 000003_add_roleid_to_users.up.sql
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
3.2.5.1 应用多次修改
migrate -database 'postgres://postgres:mysecretpassword@192.168.10.212:5432/example?sslmode=disable' -path ./migrations downmigrate -database 'postgres://postgres:mysecretpassword@192.168.10.212:5432/example?sslmode=disable' -path ./migrations up000002_add_mood_to_users.up.sql000003_add_roleid_to_users.up.sql
3.2.6 测试执行失败的情况
000001_create_users_table.up.sql000002_add_mood_to_users.up.sql000003_add_roleid_to_users.up.sql000003_add_roleid_to_users.up.sql
➜ postgres migrate -database 'postgres://postgres:mysecretpassword@192.168.10.212:5432/example?sslmode=disable' -path ./migrations down # 回滚所有操作
Are you sure you want to apply all down migrations? [y/N]
y
Applying all down migrations
no change
➜ postgres
➜ postgres
➜ postgres migrate -database 'postgres://postgres:mysecretpassword@192.168.10.212:5432/example?sslmode=disable' -path ./migrations up 2 # 应用 `000001_create_users_table.up.sql` 和 `000002_add_mood_to_users.up.sql`
1/u create_users_table (47.883494ms)
2/u add_mood_to_users (82.025579ms)
➜ postgres
➜ postgres cat migrations/000003_add_roleid_to_users.up.sql
ALTER TABLE users ADD COLUMN role_id INTEGER errtest;%%
# 修改 000003_add_roleid_to_users.up.sql 增加 errtest 到语句末尾,使得 sql 语法错误
➜ postgres migrate -database 'postgres://postgres:mysecretpassword@192.168.10.212:5432/example?sslmode=disable' -path ./migrations up 1 # 执行错误
error: migration failed: syntax error at or near "errtest" (column 46) in line 1: ALTER TABLE users ADD COLUMN role_id INTEGER errtest; (details: pq: syntax error at or near "errtest")
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
这个时候查询 postgres 数据库的 schema_migrations 表,version=3,说明当前执行到 000003, 但是 dirty=t 说明执行有错误。
example=# select * from schema_migrations ;
version | dirty
---------+-------
3 | t
(1 row)
- 1
- 2
- 3
- 4
- 5
000003_add_roleid_to_users.up.sql
postgres migrate -database 'postgres://postgres:mysecretpassword@192.168.10.212:5432/example?sslmode=disable' -path ./migrations up 1
error: migration failed: syntax error at or near "errtest" (column 60) in line 1: ALTER TABLE users ADD COLUMN IF NOT EXISTS role_id INTEGER errtest; (details: pq: syntax error at or near "errtest")
➜ postgres migrate -database 'postgres://postgres:mysecretpassword@192.168.10.212:5432/example?sslmode=disable' -path ./migrations force 3
➜ postgres migrate -database 'postgres://postgres:mysecretpassword@192.168.10.212:5432/example?sslmode=disable' -path ./migrations down 1
3/d add_roleid_to_users (34.332954ms)
➜ postgres migrate -database 'postgres://postgres:mysecretpassword@192.168.10.212:5432/example?sslmode=disable' -path ./migrations up 1
3/u add_roleid_to_users (36.832839ms)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
3.3 mysql 测试
说回前面说的测试 mysql 出现错误的问题,错误提示如下:
migrate -database mysql://root:x*xxx@192.168.10.212:3306/temp -path ./migrations up
zsh: no matches found: mysql://root:x*xxx@192.168.10.212:3306/temp
- 1
- 2
migrate -database 'mysql://root:x*xxx@192.168.10.212:3306/temp' -path ./migrations up
修正了 zsh 的问题后,再次执行 migrate up,还是提示错误:
migrate -database mysql://root:x*xxx@192.168.10.212:3306/temp -path ./migrations up
error: default addr for network '192.168.10.212:3306' unknown
- 1
- 2
这次的错误是因为 mysql 的 url 书写格式问题,mysql 的 url 需要写成 mysql://root:passwd@tcp(192.168.10.212:3306)/database 这样的格式。