數據庫遷移工具 golang-migrate

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

進入 postgres 容器運行 psql \-h localhost \-U postgres \-w \-c "create database example;" 來創建 example 數據庫。

3.2.3 創建某次數據庫變更的 sql 文件

mkdir migrations #首次執行,用於存放變更的 sql 文件

比如說這次修改我們需要新增一個 users 的表,

migrate create -ext sql -dir ./migrations -seq create_users_table

這個命令會在 migrations 目錄下生成兩個文件 000001_create_users_table.up.sql000001_create_users_table.down.sql , 000001 是某次修改的版本號,000001_create_users_table.up.sql 用來存放創建 users 表的 sql 腳本,000001_create_users_table.down.sql 用來存放回滾這次操作的 sql 腳本。migrate create 命令只負責創建文件,sql 文件的內容需要我們手動編輯。

migrations
├── 000001_create_users_table.down.sql
├── 000001_create_users_table.up.sql

編輯 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
);

編輯 000001_create_users_table.down.sql

DROP TABLE IF EXISTS users;

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

首次執行 migrate up 命令後,會在數據庫中添加 schema_migrations 表,這個表有兩個字段:version 和 dirty,version 用來表示當前數據庫對應 ./migrations 下面的那個版本,比如第一次執行 migrate up 1 後,如果執行成功了, schema_migrations 表中的 version=1,說明當前數據庫對應的是 000001_create_users_table.up.sql 這個版本 dirty=f(false),執行到 version=1 這次的變更沒有出錯。如果 dirty=t(true) 會涉及要怎麼修復這個錯誤的問題。

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)

執行 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)
example=# select * from schema_migrations;
 version | dirty
---------+-------
       1 | f
(1 row)

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)

migrations down 後,數據庫恢復到應用 create_users 之前的狀態,users 表被刪除,schema_migrations 表記錄被刪除

example=# select * from schema_migrations ;
 version | dirty
---------+-------
(0 rows)

3.2.5 應用多次修改

  1. 爲了測試 migrations up [N] 執行多次修改的情形,第二次修改我們使用事務爲 users 表增加 COLUMN,

migrate create \-ext sql \-dir ./migrations \-seq add_mood_to_users , migrations 目錄下會增加 000002_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;

000002_add_mood_to_users.down.sql

BEGIN;

ALTER TABLE users DROP COLUMN IF EXISTS mood;
DROP TYPE enum_mood;

COMMIT;
  1. 第三次修改爲 users 表增加 role_id 這個 COLUMN

migrate create \-ext sql \-dir ./migrations \-seq add_roleid_to_users , migrations 目錄下會增加 000003_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;

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

3.2.5.1 應用多次修改

  1. 執行 migrate down 回滾 migrations 目錄中所有修改。migrate \-database 'postgres://postgres:mysecretpassword@192.168.10.212:5432/example?sslmode=disable' \-path ./migrations down

  2. migrate up 表示執行 migrations 目錄中所有 up.sql 腳本。migrate \-database 'postgres://postgres:mysecretpassword@192.168.10.212:5432/example?sslmode=disable' \-path ./migrations up

  3. migrate up [N], 表示從 schema_migrations 表的 version 的值後再執行 N 個部署。假設 schema_migration 中 version=1,migrate up [2] 就會執行 migrations 目錄中 000001* 之後的 000002_add_mood_to_users.up.sql000003_add_roleid_to_users.up.sql 兩個操作。

3.2.6 測試執行失敗的情況

首先執行 migrate down 回滾所有操作,然後執行 migrate up 2 應用 000001_create_users_table.up.sql000002_add_mood_to_users.up.sql 。接下來我們修改 000003_add_roleid_to_users.up.sql ,使得 000003_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")

這個時候查詢 postgres 數據庫的 schema_migrations 表,version=3,說明當前執行到 000003, 但是 dirty=t 說明執行有錯誤。

example=# select * from schema_migrations ;
 version | dirty
---------+-------
       3 | t
(1 row)

然後修正 000003_add_roleid_to_users.up.sql 的語法錯誤(去掉多餘的 “errtest”),再次執行 migrate up 1,這個時候還是提示錯誤,因爲 version=3 的 dirty=t,這個時候需要使用 migrate force 3 來確認說 version=3 的錯誤問題已修復,而且需要執行 migrate down 1 將 version 回退到 version=2 ,才能繼續執行。

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)

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

查了一下這個錯誤是因爲我使用的 shell 是 zsh, zsh 會自動解釋 * ?等字符,而不是把 * ?留給命令 migrate 來解析,導致了錯誤,解決方案是在 ~/.zshrc 中加入:setopt no_nomatch 。另外一個解決方法是把 -database 參數的值加上引號 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

這次的錯誤是因爲 mysql 的 url 書寫格式問題,mysql 的 url 需要寫成 mysql://root:passwd@tcp(192.168.10.212:3306)/database 這樣的格式。

轉自:

blog.csdn.net/doyzfly/article/details/121096806

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Jg1xybreqW0MBOhrzoOm0w