对比 11 个 Go 数据库迁移(migration)工具
在这篇文章中,我们将对各种Go语言实现的数据库迁移工具进行比较。这篇文章写于2017年,所以有些东西可能已经过时了。
你正在学习GO吗? 你读过 The Go Programming Language & Go in Action 这两本书吗? 当我刚开始使用Go时,这两本书对我有很大帮助。如果您想根据代码实例来进行学习, 一定要看看这本书:
Go in Action.
首先,我们将比较以下工具:
- https://github.com/markbates/pop/soda
- github.com/GuiaBolso/darwin
- bitbucket.org/liamstask/goose
- github.com/go-gormigrate/gormigrat...
- github.com/mattes/migrate
- github.com/pravasan/pravasan
- github.com/rubenv/sql-migrate
- github.com/BurntSushi/migration
- github.com/DavidHuie/gomigrate
- github.com/tanel/dbmigrate
- github.com/pressly/goose
目录
TL;DR 如果您要查找架构迁移工具,可以使用:
mattes/migrate, SQL 定义的架构迁移,具有定义良好的 API, 大型数据库支持和有用的 CLI 工具。此工具被积极维护,有很多星星和一个来自goreport的A+。
rubenv/sql-migrate, 基于GO的struct
定义或SQL
定义实现的数据库架构迁移工具,并带有配置文件,迁移历史记录,生产-开发-测试环境。唯一的缺点是它在goreport的评价是B级。
markbates/pop, 如果您正在寻找ORM之类的库,请使用此工具。它具有出色的模型生成功能和用于编写架构更改的自定义DSL语言。
go-gormigrate/gormigrate, 如果您使用的是GORM,请使用此工具,此帮助程序会添加适当的架构版本控制和回滚功能。
为什么选择比较这几个工具?参见下文:
Github Stars对比
项目 | 星星数 |
---|---|
mattes/migrate | 961 |
rubenv/sql-migrate | 605 |
markbates/pop | 605 |
liamstask/goose | – bitbucket project; stars not available |
DavidHuie/gomigrate | 110 |
pressly/goose | 80 |
BurntSushi/migration | 56 |
tanel/dbmigrate | 38 |
GuiaBolso/darwin | 29 |
go-gormigrate/gormigrate | 22 |
pravasan/pravasan | 14 |
注意:我将goose排在第四位,因为它有很多观察者。
我使用Github星作为一个度量,来看看这个项目的使用范围有多广。因为几乎不可能知道到底有多少用户在使用它。我大胆地列出了在这一类中获胜的工具。
按上次更新时间比较
项目 | 创建日期 |
---|---|
markbates/pop | Feb 14, 2017 |
mattes/migrate | Feb 10, 2017 |
GuiaBolso/darwin | Feb 10, 2017 |
rubenv/sql-migrate | Feb 7, 2017 |
go-gormigrate/gormigrate | Feb 4, 2017 |
pressly/goose | Dec 9, 2016 |
DavidHuie/gomigrate | Aug 9, 2016 |
tanel/dbmigrate | Feb 23, 2016 |
pravasan/pravasan | Mar 20, 2015 |
liamstask/goose | Jan 16, 2015 |
BurntSushi/migration | Jan 25, 2014 |
注意:我在2017年2月15日星期三下午3:00删除了此信息
您通常希望使用被持续维护的项目。所以最新的更新时间可以看作是项目可维护性的度量。这是很重要的,因为如果有一个BUG,你一定希望能够提交一个PR或创建一个ISSUE,并解决该问题。被加粗显示的工具在这一比较中获胜。
通过 goreportcard 对比
项目 | 等级 |
---|---|
markbates/pop | A+ |
GuiaBolso/darwin | A+ |
go-gormigrate/gormigrate | A+ |
mattes/migrate | A |
liamstask/goose | A |
pressly/goose | A |
DavidHuie/gomigrate | A |
tanel/dbmigrate | A |
rubenv/sql-migrate | B |
BurntSushi/migration | B |
pravasan/pravasan | D |
注意:我在美国东部时间2017年2月15日星期三3:00 pm取消了此信息。
Goreportcard 允许您检查用go编写的任何开源项目的代码质量,并给出总体得分(A +,A,B ..)。我加粗了在该类别中获胜的工具。
通过可用性
进行比较在此比较中,我将尝试一些工具并给出自己的看法。
我将玩一个桌子MyGuests,它看起来像这样:
CREATE TABLE MyGuests (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
firstname VARCHAR(30) NOT NULL,
lastname VARCHAR(30) NOT NULL,
email VARCHAR(50),
reg_date TIMESTAMP
)
mattes/migrate
这是一个简单的工具,可基于文件进行迁移。它带有Go库和CLI工具,可帮助您创建SQL迁移文件并管理架构版本。让我们看一下以下CLI工具的用法示例:
$ migrate -url mysql://root@tcp(127.0.0.1:3306)/mattes -path ./migrations
create initial_users_table
创建2个文件和一个名为 schema_migrations 的表:
Version 1487240220 migration files created in ./migrations:
1487240220_initial_users_table.up.sql
1487240220_initial_users_table.down.sql
我在名为 up.sql的文件中添加了 CREATE TABLE MyGuests…语句,并在 down.sql中添加了 DROP TABLE MyGuests .. 语句
使用CLI运行迁移很简单:
$ migrate -url mysql://root@tcp(127.0.0.1:3306)/mattes
-path ./migrations up
这将创建一个表并在schema_migrations表中设置一行:
mysql> select * from schema_migrations;
+------------+
| version |
+------------+
| 1487240220 |
+------------+
1 row in set (0.00 sec)
这是运行“向下”迁移的示例,该迁移将删除表并从schema_migrations中删除一行:
$ migrate -url mysql://root@tcp(127.0.0.1:3306)/mattes
-path ./migrations down
CLI工具还允许转到特定的架构版本,回滚先前的 n 迁移等。
所提供的go库也很简单(https://godoc.org/github.com/mattes/migrate/migrate),它允许您从代码中运行迁移,并为您提供同步和异步实现。可能您只会从代码中使用UpSync功能。看下面的例子:
package main
import (
"fmt"
_ "github.com/mattes/migrate/driver/mysql"
"github.com/mattes/migrate/migrate"
)
func main() {
fmt.Println("Hello")
allErrors, ok := migrate.UpSync("mysql://root@tcp(127.0.0.1:3306)/mattes", "./migrations")
if !ok {
fmt.Println(allErrors)
}
}
我喜欢这个库,因为它很简单。它支持PostgreSQL,Cassandra,SQLite,MySQL,Neo4j,Ql,MongoDB,CrateDb。但它有一个警告:MySQL支持仅是实验性的。
liamstask/goose
玩这个库对我来说有点痛苦。在大约20分钟内,我无法确定连接信息出了什么问题。我不断收到无效的DBConf 错误,没有任何解释:
2017/02/16 13:14:54 Invalid DBConf:
{mysql root@tcp(127.0.0.1:3306)/goose }
现在看来,我在指定数据库类型后留了一个空格!
因此在goose中,您必须创建一个名为 db的目录并添加一个名为 dbconf.yaml **的文件,其中包含连接信息。这是我的文件的外观:
development:
driver: mysql
open: root@tcp(127.0.0.1:3306)/goose
在此配置中,还可以选择不同的 SQL 实现方式并导入其他数据库驱动。
使用 goose 创建数据迁移非常的简单:
goose create initial_users_table
上述语句回创建一个名为 20170216132820_initial_users_table.go 的文件,其中包含了两个 go 函数:
func Up_20170216132820(txn *sql.Tx) {
}
func Down_20170216132820(txn *sql.Tx) {
}
这里展示了如何填充这些函数:
// UP 方法会在此数据迁移时执行
func Up_20170216132820(txn *sql.Tx) {
res, err := txn.Exec(`CREATE TABLE MyGuests (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
firstname VARCHAR(30) NOT NULL,
lastname VARCHAR(30) NOT NULL,
email VARCHAR(50),
reg_date TIMESTAMP
)`)
fmt.Println(res)
fmt.Println(err)
}
// Down 方法会在此迁移回滚时执行
func Down_20170216132820(txn *sql.Tx) {
res, err := txn.Exec("DROP TABLE MyGuests;")
fmt.Println(res)
fmt.Println(err)
}
执行/回滚迁移也非常的容易:
goose up
goose down
在 goose 内部维护了一个名为 goose_db_version 的表格:
mysql> select * from goose_db_version;
+----+----------------+------------+---------------------+
| id | version_id | is_applied | tstamp |
+----+----------------+------------+---------------------+
| 1 | 0 | 1 | 2017-02-16 13:37:20 |
| 2 | 20170216132820 | 1 | 2017-02-16 13:37:47 |
| 3 | 20170216132820 | 0 | 2017-02-16 13:39:32 |
| 4 | 20170216132820 | 1 | 2017-02-16 13:40:04 |
| 5 | 20170216134743 | 1 | 2017-02-16 13:51:30 |
| 6 | 20170216134743 | 0 | 2017-02-16 13:51:34 |
+----+----------------+------------+---------------------+
6 rows in set (0.00 sec)
此工具还允许使用指定的 SQL 文件执行迁移,默认支持 postgres,mysql,sqlite3 并封装了一个 go 库。我最喜欢在配置文件中指定链接信息,这会简化编写命令行工具的工作。此外,将数据库迁移写作 go 代码就很有意思。总的来说,这个工具还算靠谱。
markbates/pop
pop 更像是一个 “ORM”,它可以帮助开发者创建模型和 sql 执行计划。pop 也带有命令行下的数据迁移工具,其名为 soda,还有用于指定迁移的 DSL 工具,其名为 fizz。
首先,您必须在 database.yaml 文件中指定数据库连接配置。我的看起来像这样:
development:
dialect: "mysql"
database: "pop"
host: "localhost"
port: "3306"
user: "root"
password: ""
然后,您可以使用CLI工具创建/删除数据库:
soda create -e development
soda drop -e development
使用基于 fizz DSL的迁移脚本生成模型很简单:
soda generate model MyGuest firstname:text lastname:text email:text
reg_date:timestamp
生成的DSL如下所示:
create_table("my_guests", func(t) {
t.Column("id", "uuid", {"primary": true})
t.Column("firstname", "text", {})
t.Column("lastname", "text", {})
t.Column("email", "text", {})
t.Column("reg_date", "timestamp", {})
})
还有 model:
type MyGuest struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
Firstname string `json:"firstname" db:"firstname"`
Lastname string `json:"lastname" db:"lastname"`
Email string `json:"email" db:"email"`
RegDate time.Time `json:"reg_date" db:"reg_date"`
}
向上/向下迁移:
soda migrate up
soda migrate down
在迁移时,Fizz DSL产生了以下模式:
mysql> desc my_guests;
+------------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+----------+------+-----+---------+-------+
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
| id | char(36) | NO | PRI | NULL | |
| firstname | text | NO | | NULL | |
| lastname | text | NO | | NULL | |
| email | text | NO | | NULL | |
| reg_date | datetime | NO | | NULL | |
+------------+----------+------+-----+---------+-------+
7 rows in set (0.00 sec)
此工具在内部维护一个名为 schema_migration 的表,其中包含模式版本号。
我非常喜欢这个库,但是感觉您只应该使用它,然后您就在寻找像库这样的“ ORM”。生成模型和迁移看起来很酷!另外,Fizz DSL的奖励积分看起来很像go!
缺点之一是pop仅支持:PostgreSQL(> = 9.3),MySQL(> = 5.7)和SQLite(> = 3.x)。
go-gormigrate/gormigrate
Gormigrate 是GORM库的迁移帮助器。该帮助程序添加了适当的架构版本控制和回滚功能。我喜欢结构列表中的模式版本控制+模式迁移定义。这是MyGuests示例的外观:
func main() {
db, err := gorm.Open("mysql",
"root@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local")
if err != nil {
panic("failed to connect database")
}
if err = db.DB().Ping(); err != nil {
log.Fatal(err)
}
db.LogMode(true)
defer db.Close()
m := gormigrate.New(db, gormigrate.DefaultOptions,
[]*gormigrate.Migration{
{
ID: "201702200906",
Migrate: func(tx *gorm.DB) error {
type MyGuest struct {
gorm.Model
Firstname string
Lastname string
Email string
RegDate time.Time
}
return tx.AutoMigrate(&MyGuest{}).Error
},
Rollback: func(tx *gorm.DB) error {
return tx.DropTable("MyGuest").Error
},
},
})
if err = m.Migrate(); err != nil {
log.Fatalf("Could not migrate: %v", err)
}
}
此迁移创建了一个如下结构的表格:
mysql> desc my_guests;
+------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
| deleted_at | timestamp | YES | MUL | NULL | |
| firstname | varchar(255) | YES | | NULL | |
| lastname | varchar(255) | YES | | NULL | |
| email | varchar(255) | YES | | NULL | |
| reg_date | timestamp | YES | | NULL | |
+------------+------------------+------+-----+---------+----------------+
8 rows in set (0.00 sec)
我确信我会在 GORM 中使用它。
rubenv/sql-migrate
sql-migrate 看起来跟 goose 很像,在 yaml 配置文件中指定好连接,迁移配置使用 SQL 文件的形式编写。其拥有命令行工具,可用于生成迁移模板。
$ sql-migrate new MyGuests
我的结构变更后看起来像这样:
-- +migrate Up
CREATE TABLE MyGuests (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
firstname VARCHAR(30) NOT NULL,
lastname VARCHAR(30) NOT NULL,
email VARCHAR(50),
reg_date TIMESTAMP
);
-- +migrate Down
DROP TABLE MyGuests;
使用命令行工具应用迁移非常简单:
$ sql-migrate up
Applied 1 migration
它还有一个外观漂亮的库,带有定义良好的 API,支持在指定结构中或在文件夹下执行迁移。我能想到的唯一的缺点是 goreport 给了此库 B 评分。
DavidHuie/gomigrate
这是个非常简单的工具,仅允许在 go 代码中执行迁移:
err := migrator.Migrate()
err := migrator.Rollback()
迁移需要使用 sql 文件进行定义,文件名需为 { id }}{{ name }}{{ “up” or “down” }}.sql,需要自行管理这些文件,因为这只是一个库而非完整工具。mattes/migrate 似乎涵盖了这些功能,并且添加了更多的特性,因此我更倾向于使用它而非 DavidHuie/gomigrate。
GuiaBolso/darwin
这是一个库,可跟踪结构中的架构更改,并且仅允许向上迁移。我喜欢将所有迁移存储在一个切片中的想法:
var (
migrations = []darwin.Migration{
{
Version: 1,
Description: "Creating table MyGuests",
Script: `CREATE TABLE MyGuests (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
firstname VARCHAR(30) NOT NULL,
lastname VARCHAR(30) NOT NULL,
email VARCHAR(50),
reg_date TIMESTAMP
)`,
},
}
)
func main() {
database, err := sql.Open("mysql", "root@tcp(127.0.0.1:3306)/darwin")
if err != nil {
log.Fatal(err)
}
driver := darwin.NewGenericDriver(database, darwin.MySQLDialect{})
d := darwin.New(driver, migrations, nil)
err = d.Migrate()
if err != nil {
log.Println(err)
}
}
我认为关于该库没有更多要说的,但是我想这是一件好事。
tanel/dbmigrate
PostgreSQL或Cassandra的简单库。运行使用其文件名排序的迁移(.sql或.cql文件)。遮罩/迁移似乎涵盖了相同的功能并添加了更多功能,因此我宁愿在此之上使用它。
pressly/goose
这是goose的fork, 它放弃了对配置文件和自定义驱动程序的支持。迁移可以使用与 database/sql. 这个工具看起来像 liamstask/goose 和 mattes/migrate 有了孩子!
这两个项目都需要好东西:良好的CLI,无需配置,使用.sql或.go文件编写迁移,将迁移历史记录存储在goose_db_version表中。但是该工具缺少一些东西:它不支持Cassandra或任何其他非SQL数据库,CLI只能迁移SQL文件。
然后尝试使用它我在使用此工具时遇到了问题:
我创建了基于SQL的迁移:
$ goose mysql "root@tcp(127.0.0.1:3306)/pressly" create initial_users_table sql
Created sql migration at 20170301085637_initial_users_table.sql
在20170301085637_initial_users_table.sql文件中填充以下内容:
-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied
CREATE TABLE MyGuests (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
firstname VARCHAR(30) NOT NULL,
lastname VARCHAR(30) NOT NULL,
email VARCHAR(50),
reg_date TIMESTAMP
)
-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back
DROP TABLE MyGuests;
$ goose mysql "root@tcp(127.0.0.1:3306)/pressly" up
2017/03/01 08:57:41 WARNING: Unexpected unfinished SQL query: -- +goose Up
-- SQL in section 'Up' is executed when this migration is applied
CREATE TABLE MyGuests (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
firstname VARCHAR(30) NOT NULL,
lastname VARCHAR(30) NOT NULL,
email VARCHAR(50),
reg_date TIMESTAMP
). Missing a semicolon?
OK 20170301085637_initial_users_table.sql
goose: no migrations to run. current version: 20170301085637
$ goose mysql "root@tcp(127.0.0.1:3306)/pressly" down
2017/03/01 08:57:47 FAIL 20170301085637_initial_users_table.sql (Error 1051: Unknown table 'pressly.myguests'), quitting migration.
$ goose mysql "root@tcp(127.0.0.1:3306)/pressly" up
goose: no migrations to run. current version: 20170301085637
我确实想念分号了,但是这个工具写到它的表中,迁移成功运行了,所以我处于表不存在的状态,但是工具认为它已经存在。
mysql> select * from goose_db_version;
+----+----------------+------------+---------------------+
| id | version_id | is_applied | tstamp |
+----+----------------+------------+---------------------+
| 1 | 0 | 1 | 2017-03-01 08:44:45 |
| 2 | 20170301085637 | 1 | 2017-03-01 08:57:41 |
+----+----------------+------------+---------------------+
我不得不手动删除表goose_db_version中的行并重新开始迁移…
之后,一切正常:
$ pressly goose mysql "root@tcp(127.0.0.1:3306)/pressly" up
OK 20170301085637_initial_users_table.sql
goose: no migrations to run. current version: 20170301085637
$ pressly goose mysql "root@tcp(127.0.0.1:3306)/pressly" down
OK 20170301085637_initial_users_table.sql
我也尝试基于go代码运行迁移,但未成功:
我创建了迁移并编译了 回购中提供的示例cmd文件 创建了go迁移
$ goose mysql "root@tcp(127.0.0.1:3306)/pressly" create initial_users_table
Created go migration at 20170301083810_initial_users_table.go
并填写了我的迁移代码:
package migration
import (
"database/sql"
"fmt"
"github.com/pressly/goose"
)
func init() {
goose.AddMigration(Up_20170301083810, Down_20170301083810)
}
func Up_20170301083810(tx *sql.Tx) error {
res, err := tx.Exec(`CREATE TABLE MyGuests (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
firstname VARCHAR(30) NOT NULL,
lastname VARCHAR(30) NOT NULL,
email VARCHAR(50),
reg_date TIMESTAMP
)`)
fmt.Println(res)
return err
}
func Down_20170301083810(tx *sql.Tx) error {
res, err := txn.Exec("DROP TABLE MyGuests;")
fmt.Println(res)
return err
}
然后我尝试运行:
./pressly --dir=migrations/ mysql "root@tcp(127.0.0.1:3306)/pressly" up
2017/03/01 09:02:24 FAIL 00002_rename_root.go
(Error 1146: Table 'pressly.users' doesn't exist), quitting migration.
我的迁移目录仅包含一个名为20170301083810_initial_users_table.go的文件,没有名为 00002_rename_root.go的文件,所以我不知道此工具到底在做什么,但我真的不喜欢它尝试运行名为rename_root.go的文件,我没有写过,对此一无所知。
因此,请谨慎使用此工具!
So be careful with this tool!
我不得不手动删除表goose_db_version中的行并重新开始迁移…
之后
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: