数据库同步_migrations 调研

1,361 阅读7分钟

migrations 调研

migrations 使用场景

  • 团队开发中,每个开发人员对于数据库都修改都必须手动记录,上线时需要人工整理,运维成本极高。而且在多个开发者之间数据结构同步也是很大的问题。Migrations 把数据库变更加入到代码中和代码一起进行版本管理,很好的解决了上述问题。

golang migrations

介绍

  • 根据每个迁移文件来说,migrate 库都需要一些规则。迁移文件必须命名为 1_create_XXX.up.sql 和 1 _create_xxx.down.sql。所以基本上,每个迁移都应该有一个 up.sql 和一个 down.sql 文件。在实际运行迁移时将执行 up.sql 文件,而在尝试回滚时将执行 down.sql 文件。

  • 单个逻辑迁移表示为两个单独的迁移文件,一个从上一个版本迁移到指定的版本,另一个从上一个版本回退到指定的版本。 这些迁移可以由任何一个受支持的迁移源提供。

  • 迁移文件的顺序和方向由它们所使用的文件名决定。 Migrate 希望迁移的文件名的格式是:

{version}_{title}.up.{extension}
{version}_{title}.down.{extension}
  • 每个迁移的标题都是未使用的,并且仅用于可读性。 类似地,迁移文件的扩展名不会被库检查,并且应该是正在使用的数据库的适当格式。

  • 迁移的版本可以表示为任何64位无符号整数。 所有迁移按照版本号增加的顺序向上应用,按版本号减少的顺序向下应用。

  • 常见的版本控制方案包括递增整数:

1_initialize_schema.down.sql
1_initialize_schema.up.sql
2_add_table.down.sql
2_add_table.up.sql
...
  • 或者使用时间戳:
1500360784_initialize_schema.down.sql
1500360784_initialize_schema.up.sql
1500445949_add_table.down.sql
1500445949_add_table.up.sql
...
  • 任何将不同的、递增的整数作为版本的方案都是有效的。

golang-migrate/migrate 使用

通过migrate CLI 操作(下载方式)

  • 创建一个目录 migrations,里面存放我们需要迁移的对象
  • 创建迁移文件
migrate create -ext sql -dir migrations create_users_table
// 执行这条命令会创建两个文件,分别是
20191228100730_create_test29_table.down.sql  20191228100730_create_test29_table.up.sql
  • 20191228100730是和时间有关的一个标识,用来区分我们的migration的版本。
  • 在20191228100730_create_test29_table.up.sql中写入创建users表的操作:
CREATE TABLE  test_29(
     id INT,
     name VARCHAR(100) NOT NULL,
     password VARCHAR(40) NOT NULL,
     PRIMARY KEY ( id )
);
  • 对应的我们就需要在20191228100730_create_test29_table.down.sql中删除这张表:
DROP table IF EXISTS test_29;
  • 终端执行命令
 migrate -verbose -source file:/Users/js/work/powerlaw/go/controller/migrations -database  "mysql://root:xiamingyu123.@tcp(127.0.0.1:3306)/solegalee?collation=utf8mb4_bin&parseTime=true&loc=Local" up 1

日志

2019/12/28 10:38:57 Start buffering 20191228103644/u create_users_table
2019/12/28 10:38:57 Read and execute 20191228103644/u create_users_table
2019/12/28 10:38:57 Finished 20191228103644/u create_users_table (read 11.844954ms, ran 29.308199ms)
2019/12/28 10:38:57 Finished after 44.405306ms
2019/12/28 10:38:57 Closing source and database
  • 命令介绍

    • up: 表示执行up文件
    • 1 : 表示本次操作更新1个版本(可以一次up多个版本)
    • -source : migrations 文件来源
    • -verbose:打印详细日志
    • -database 数据库连接(数据库连接字符串是通过URL指定的。 URL格式取决于驱动程序,但通常采用以下格式:dbdriver://用户名:password@host:port/dbname?option1=true&option2=false 以下字符需要转义: !, #, $, %, &, ', (, ), *, +, ,, /, :, ;, =, ?, @, [, ])
  • 执行成功后,数据库会新建 schema_migrations表同时插入一条记录;同时会新增一个test_29表

version	        dirty
20191228103644	0
  • version :表示插入的版本号,也是sql文件的前缀
  • dirty: 0表示当前版本执行成功,1表示执行失败
  • 接下来执行下面语句回退版本
 migrate -verbose -source file:/Users/js/work/powerlaw/go/controller/migrations -database  "mysql://root:xiamingyu123.@tcp(127.0.0.1:3306)/solegalee?collation=utf8mb4_bin&parseTime=true&loc=Local" down 1
  • 日志
2019/12/28 10:41:55 Start buffering 20191228103644/d create_users_table
2019/12/28 10:41:55 Read and execute 20191228103644/d create_users_table
2019/12/28 10:41:55 Finished 20191228103644/d create_users_table (read 21.480679ms, ran 29.485445ms)
2019/12/28 10:41:55 Finished after 54.122525ms
2019/12/28 10:41:55 Closing source and database
  • 执行成功后,test_29 数据表会被删除,同时schema_migrations表中的记录会被删除

  • 如果我们正常执行了 up 1 的操作,然后使用命令

    migrate create -ext sql -dir migrations create_test30_table
    
  • 这时会有4个文件

    20191228103644_create_users_table.down.sql
    20191228103644_create_users_table.up.sql
    20191228104751_create_test30_table.down.sql
    20191228104751_create_test30_table.up.sql
    
  • 接下来我们把新创建的sql文件里写入对应的建表以及删除表的语句

    // up
    CREATE TABLE  test_30(
       id INT,
       name VARCHAR(100) NOT NULL,
       password VARCHAR(40) NOT NULL,
       PRIMARY KEY ( id )
    );
    // down 
    DROP table IF EXISTS test_30;
    
  • 然后执行下面命令使20191228104751_create_test30_table.up.sql 生效

    migrate -verbose -source file:/Users/js/work/powerlaw/go/controller/migrations -database  "mysql://root:xiamingyu123.@tcp(127.0.0.1:3306)/solegalee?collation=utf8mb4_bin&parseTime=true&loc=Local" up 1
    
    • 如果我们一次创建了2个up sql文件,并且想同时生效,应该使用下面的命令,一次执行两个版本up操作
    migrate -verbose -source file:/Users/js/work/powerlaw/go/controller/migrations -database  "mysql://root:xiamingyu123.@tcp(127.0.0.1:3306)/solegalee?collation=utf8mb4_bin&parseTime=true&loc=Local" up 2
    

通过导入模块在代码里使用

package main

import (
	"database/sql"
	"flag"
	"fmt"
	"log"
	"os"

	_ "github.com/go-sql-driver/mysql"
	"github.com/golang-migrate/migrate/v4"
	"github.com/golang-migrate/migrate/v4/database/mysql"
	_ "github.com/golang-migrate/migrate/v4/source/file"
)

func main() {

	var migrationDir = flag.String("migration.files", "./migrations", "Directory where the migration files are located ?")
	var mysqlDSN = flag.String("mysql.dsn", os.Getenv("MYSQL_DSN"), "Mysql DSN")

	flag.Parse()

	db, err := sql.Open("mysql", *mysqlDSN)
	if err != nil {
		log.Fatalf("could not connect to the MySQL database... %v", err)
	}

	if err := db.Ping(); err != nil {
		log.Fatalf("could not ping DB... %v", err)
	}

	// Run migrations
	driver, err := mysql.WithInstance(db, &mysql.Config{})
	if err != nil {
		log.Fatalf("could not start sql migration... %v", err)
	}

	m, err := migrate.NewWithDatabaseInstance(
		fmt.Sprintf("file://%s", *migrationDir), // file://path/to/directory
		"mysql", driver)

	if err != nil {
		log.Fatalf("migration failed... %v", err)
	}

  
	if err := m.Up(); err != nil && err != migrate.ErrNoChange {
		log.Fatalf("An error occurred while syncing the database.. %v", err)
	}

	log.Println("Database migrated")
	// actual logic to start your application
	os.Exit(0
  • 如果有多个up SQL文件,m.Up() 会一起全部执行。
  • 如果有多个down SQL文件,m.Down() 会一起全部执行。
  • m.Version() 返回当前的版本号,以及执行情况
没有执行过up时:
0 false no migration
执行过up时:
20191228105602 false <nil>
  • 通过设置step,up 一个版本(会把当前新增的up sql 文件全部执行)或者down 一个版本
	err = m.Steps(1)
	if err != nil {
		utils.PanicErr(utils.WrapErr(err))
	}
	if err := m.Up(); err != nil && err != migrate.ErrNoChange {
		utils.PanicErr(utils.WrapErr(err))
	}
  // Steps(-1) 表示回滚一个版本
  	err = m.Steps(-1)
	if err != nil {
		utils.PanicErr(utils.WrapErr(err))
	}
	if err := m.Down(); err != nil && err != migrate.ErrNoChange {
		utils.PanicErr(utils.WrapErr(err))
	}
	fmt.Println(m.Version())
  
  • 如果一个up 文件里面的SQL 有错误
CREATE TABLE  test_30(
     id INTs,
     name VARCHAR(100) NOT NULL,
     password VARCHAR(40) NOT NULL,
     PRIMARY KEY ( id )
);
  • 这时执行,
ERRO[0000]/Users/js/work/powerlaw/go/controller/internal/utils/tools.go:50 powerlaw.ai/solegal-ee/controller/internal/utils.LogErr() migration failed in line 0: CREATE TABLE  test_30(
     id INTs,
     name VARCHAR(100) NOT NULL,
     password VARCHAR(40) NOT NULL,
     PRIMARY KEY ( id )
); (details: Error 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INTs,
     name VARCHAR(100) NOT NULL,
     password VARCHAR(40) NOT NULL,
     ' at line 2)

  • 数据库里会将这个版本的记录 dirty 设置为1,表示未执行成功
version	dirty
20191228104751	1
  • 将SQL修改正确后再次执行,仍然报错
 Dirty database version 20191228104751. Fix and force version.
  • 这时在代码里加 m.Force(20191228104751)
err = m.Force(20191228104751)
	if err != nil {
		utils.PanicErr(utils.WrapErr(err))
	}
	if err := m.Up(); err != nil && err != migrate.ErrNoChange {
		utils.PanicErr(utils.WrapErr(err))
	}
  • 数据库 schema_migrations表的记录修改成功,同时up SQL 也执行成功
version	dirty
20191228104751	0

更多内容欢迎关注我的个人公众号“韩哥有话说”,100G人工智能学习资料,大量后端学习资料等你来拿。

qrcode_for_gh_3214f9e3470a_258.jpg