數(shù)據(jù)庫(kù)多版本遷移

flyway是什么?

Flyway是一個(gè)簡(jiǎn)單開(kāi)源數(shù)據(jù)庫(kù)版本控制器(約定大于配置),主要提供migrate、clean、info、validate、baseline、repair等命令。它支持SQL(PL/SQL、T-SQL)方式和Java方式,支持命令行客戶端等,還提供一系列的插件支持(Maven、Gradle、SBT、ANT等)。

Flyway是獨(dú)立于數(shù)據(jù)庫(kù)的應(yīng)用、管理并跟蹤數(shù)據(jù)庫(kù)變更的數(shù)據(jù)庫(kù)版本管理工具。用通俗的話講,F(xiàn)lyway可以像Git管理不同人的代碼那樣,管理不同人的sql腳本,從而做到數(shù)據(jù)庫(kù)同步。

注意:

flyway在執(zhí)行腳本時(shí),會(huì)在源數(shù)據(jù)表中檢查checksum值,并確定上次運(yùn)行到哪一個(gè)腳本文件,本次執(zhí)行時(shí)從下一條腳本文件開(kāi)始執(zhí)行。所以編寫腳本的時(shí)候不要去修改原有的腳本內(nèi)容,并且新的腳本版本號(hào)要連續(xù)

為什么要使用flyway

通常在項(xiàng)目開(kāi)始時(shí)會(huì)針對(duì)數(shù)據(jù)庫(kù)進(jìn)行全局設(shè)計(jì),但在開(kāi)發(fā)產(chǎn)品新特性過(guò)程中,難免會(huì)遇到需要更新數(shù)據(jù)庫(kù)Schema的情況,比如:添加新表,添加新字段和約束等,這種情況在實(shí)際項(xiàng)目中也經(jīng)常發(fā)生。那么,當(dāng)開(kāi)發(fā)人員完成了對(duì)數(shù)據(jù)庫(kù)更的SQL腳本后,如何快速地在其他開(kāi)發(fā)者機(jī)器上同步?并且如何在測(cè)試服務(wù)器上快速同步?以及如何保證集成測(cè)試能夠順利執(zhí)行并通過(guò)呢?

在日常的開(kāi)發(fā)中,我們使用git管理代碼的版本,那么數(shù)據(jù)庫(kù)的版本呢?使用flyway。

個(gè)人認(rèn)為,可以大概的將flyway理解為數(shù)據(jù)庫(kù)的git,方便多人協(xié)作及記錄。

git:讓你和同事更加輕松的維護(hù)同一個(gè)項(xiàng)目,你可以很方便的獲取到他最新提交的改動(dòng)。

flyway:讓你及時(shí)的知道同事對(duì)數(shù)據(jù)庫(kù)的改動(dòng)并且能夠自動(dòng)在你的本地執(zhí)行這些改動(dòng)。

解決的問(wèn)題

  1. 和同事同時(shí)維護(hù)一個(gè)項(xiàng)目,同時(shí)對(duì)數(shù)據(jù)庫(kù)做出了一些修改,我在使用git拉取了最新的代碼之后,運(yùn)行總是報(bào)錯(cuò),需要自己去重新執(zhí)行一遍該表的創(chuàng)建語(yǔ)句來(lái)在本地進(jìn)行創(chuàng)建,使用flyway后,拉取最新代碼的同時(shí)會(huì)拉取最新的sql文件,同時(shí)在服務(wù)啟動(dòng)時(shí)自動(dòng)創(chuàng)建數(shù)據(jù)表,對(duì)一些和自己無(wú)關(guān)的數(shù)據(jù)表完全不用關(guān)心了。

  2. 新接手一個(gè)項(xiàng)目,在本地進(jìn)行開(kāi)發(fā)調(diào)試,本地新建數(shù)據(jù)庫(kù)后,需要執(zhí)行一遍建表語(yǔ)句,使用flyway可以自動(dòng)的創(chuàng)建該項(xiàng)目的所有表格。

其他相似工具

  1. 數(shù)據(jù)庫(kù)遷移工具
github.com/golang-migrate/migrate
  1. git類型的數(shù)據(jù)庫(kù)dolt
github.com/liquidata-inc/dolt   // 線上關(guān)系型數(shù)據(jù)庫(kù)

(不適合) goway

使用方法:

https://blog.csdn.net/poem_2010/article/details/86241999

https://blog.csdn.net/weixin_39827798/article/details/111865637

安裝對(duì)應(yīng)的go庫(kù)

go get github.com/poemp/goway  // 使用的人很少

配置

需要參數(shù)配置

type WayConfigure struct {
    Host       string
    Port       string
    User       string
    Password   string
    DbName     string
    SearchPath string // 數(shù)據(jù)庫(kù)
    Table      string // 版本記錄表名
    Path       string // 存放地址
}

首先需要做配置, 請(qǐng)?jiān)趩?dòng)的時(shí)候重寫

inter.DefaultTableDataSource = func() inter.WayConfigure {
        return inter.WayConfigure{
            Host:       host,
            Port:       port,
            User:       user,
            Password:   password,
            DbName:     dbName,
            SearchPath: searchPath,
            Table:      table,
            Path:       path,
        }
    }

然后, 調(diào)用手動(dòng)執(zhí)行

way.Flyway{}.Execu()
package main

import (
   "fmt"
   "github.com/jinzhu/gorm"
   "github.com/lunny/log"
   "github.com/poemp/goway/inter"
   "github.com/poemp/goway/way"
)

func main() {
   GetWayBD()
}

// GetWayBD 獲取數(shù)據(jù)庫(kù)連接
func GetWayBD() *gorm.DB {
   // 關(guān)閉
   defer func() {
      if e := recover(); e != nil {
         fmt.Println(fmt.Sprintf("recover from a fatal error : %v", e))
      }
   }()

   inter.DefaultTableDataSource = func() inter.WayConfigure {
      return inter.WayConfigure{
         Host:       "127.0.0.1",
         Port:       "9999",
         User:       "root",
         Password:   "123456",
         DbName:     "WorkStream",
         SearchPath: "searchPath",
         Table:      "table",
         Path:       "path",
      }
   }
   way.Flyway{}.Execu()
   c := inter.DefaultTableDataSource()
   source := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=%s password=%s",
      c.Host, c.Port, c.User, c.DbName, "disable", c.Password)
   log.Info("way datasource :" + source)
   // 支持其他的數(shù)據(jù)庫(kù), 需要修改這兒, 并且需要在代碼中添加 其他數(shù)據(jù)庫(kù)的驅(qū)動(dòng)
   // 就像我使用的 post 數(shù)據(jù)庫(kù) 是需要在import中加入:
   // _ "github.com/jinzhu/gorm/dialects/postgres"
   db, err := gorm.Open("postgres", source)
   db.SingularTable(true)
   db.LogMode(true)
   if err != nil {
      panic(err)
   }
   return db
}

踩坑點(diǎn)

需要修改 github.com/poemp/goway 里的db_config.go 依賴
github.com/go-xorm/core --->  xorm.io/core
image.png

(適合) golang-migrate

基本使用案例

Go migrate文檔: https://pkg.go.dev/github.com/golang-migrate/migrate#New

簡(jiǎn)單的使用案例

cmd使用

https://github.com/golang-migrate/migrate/tree/master/cmd/migrate

Windows下載地址

https://github.com/golang-migrate/migrate/releases/download/v4.15.1/migrate.windows-amd64.zip

注: 下載完畢之后將里面的migrate.exe文件復(fù)制到咱們想要執(zhí)行的目錄下即可

Linux下載地址

https://github.com/golang-migrate/migrate/releases/download/v4.15.1/migrate.linux-arm64.tar.gz

golang-migrate使用

package main

import (
    "database/sql"
    "fmt"
    "github.com/golang-migrate/migrate/v4"
    "github.com/golang-migrate/migrate/v4/database/mysql"
    _ "github.com/golang-migrate/migrate/v4/source/file"
    "log"
)

func main() {
    // 連接數(shù)據(jù)庫(kù)
    db, _ := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/workstream")
    
    if err := db.Ping(); err != nil {
        log.Fatalf("could not ping DB... %v", err)
    }

    // 開(kāi)始遷移
    driver, _ := mysql.WithInstance(db, &mysql.Config{})
    m, err := migrate.NewWithDatabaseInstance(
        fmt.Sprintf("file://%s", "./migrations"), // file://path/to/directory
        "mysql", driver)
    if err != nil {
        log.Fatalf("migration failed... %v", err)
    }
    // 執(zhí)行操作: up --> 更新,  down --> 回滾
    if err = m.Up(); err != nil && err != migrate.ErrNoChange {
        fmt.Printf("An error occurred while syncing the database.. %v", err)
    }
}

參考文章:

(重點(diǎn): 函數(shù)的使用方法)https://blog.csdn.net/Matthew__M/article/details/123427027

(重點(diǎn): migrate在gin中的應(yīng)用)https://blog.csdn.net/weixin_26737625/article/details/108494299

(重點(diǎn): migrate cli 實(shí)踐)https://a2htray.github.io/2021/02/06/golang-migrate-usage-with-postgresql/

(重點(diǎn): go migrate 測(cè)試)https://blog.csdn.net/doyzfly/article/details/121096806

(重點(diǎn): go migrate 簡(jiǎn)單應(yīng)用)https://www.cnblogs.com/super-egg/p/15080547.html

(重點(diǎn)) 項(xiàng)目中的使用

命令行用法

// 命令案例:  查看當(dāng)前數(shù)據(jù)庫(kù)版本
migrate -database mysql://root:123456@tcp(127.0.0.1:3306)/workstream -path . version

// 在當(dāng)前migration文件夾里插入一個(gè)兩個(gè)表(up, down)
migrate -database mysql://root:123456@tcp(127.0.0.1:3306)/workstream -path . create -ext sql -dir . -seq alter_users_column

------------------------------------------------------------------------------------------------------

Usage: migrate OPTIONS COMMAND [arg...]
       migrate [ -version | -help ]

Options:
  -source            遷移的位置 (driver://url)
  -path              sql腳本位置 -source=file://path
  -database          對(duì)此數(shù)據(jù)庫(kù)運(yùn)行遷移 (driver://url)
  -prefetch N        在執(zhí)行之前要提前加載的遷移數(shù) (default 10)
  -lock-timeout N    允許 N 秒獲取數(shù)據(jù)庫(kù)鎖定 (default 15)
  -verbose           打印詳細(xì)日志記錄
  -version           打印 migrate 版本
  -help              打印用法

Commands:
  create [-ext E] [-dir D] [-seq] [-digits N] [-format] [-tz] NAME
         Create a set of timestamped up/down migrations titled NAME, in directory D with extension E.
         Use -seq    生成 N 位數(shù)字的順序上/下遷移。 
         Use -format 指定 Go 時(shí)間格式字符串。注意:具有相同時(shí)間的遷移會(huì)導(dǎo)致"重復(fù)遷移版本"錯(cuò)誤。
         Use -tz     指定生成非順序遷移時(shí)將使用的時(shí)區(qū)(默認(rèn)值:UTC)

  goto V             遷移到第 V 個(gè)數(shù)據(jù)庫(kù)版本
  up [N]             應(yīng)用所有或 N 個(gè)向上遷移
  down [N] [-all]    應(yīng)用所有或 N 個(gè)向下遷移
  drop [-f]          (強(qiáng)制) 刪除所有數(shù)據(jù)庫(kù)中的數(shù)據(jù)
  force V            設(shè)置版本 V 但不運(yùn)行遷移(忽略臟狀態(tài))
  version            打印當(dāng)前遷移版本

Goland DDL映射

使用步驟

最終目的: 一句話概括, 將我們本地的sql腳本和遠(yuǎn)程數(shù)據(jù)庫(kù)連接上! ! !

步驟1.png

步驟2.png

步驟3.png

步驟4.png

缺點(diǎn):

只能映射sql腳本創(chuàng)建表的語(yǔ)句, 其他修改列字段相關(guān)的操作不支持, 且同步的時(shí)候會(huì)多出現(xiàn)一些key, 容易操作失誤, 且版本回退沒(méi)有清晰地表示, 只有相應(yīng)時(shí)間點(diǎn)的改動(dòng), 不太專業(yè).

項(xiàng)目場(chǎng)景

當(dāng)線上數(shù)據(jù)庫(kù)存在數(shù)據(jù), 且需要后續(xù)添加, 修改, 刪除字段

線上數(shù)據(jù)庫(kù)能否回退版本并且有清晰地版本號(hào), 令開(kāi)發(fā)者一目了然

支持多人團(tuán)隊(duì)協(xié)作開(kāi)發(fā), 且功能全面

后面更新項(xiàng)目場(chǎng)景的解決問(wèn)題......

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容