对比 11 个 Go 数据库迁移(migration)工具

Go

在这篇文章中,我们将对各种Go语言实现的数据库迁移工具进行比较。这篇文章写于2017年,所以有些东西可能已经过时了。

你正在学习GO吗? 你读过 The Go Programming Language & Go in Action 这两本书吗? 当我刚开始使用Go时,这两本书对我有很大帮助。如果您想根据代码实例来进行学习, 一定要看看这本书:
 Go in Action.

首先,我们将比较以下工具:

目录

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 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://povilasv.me/go-schema-migration-...

译文地址:https://learnku.com/go/t/52493

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!