golang数据库schema迁移嵌入二进制文件插件库schema的使用

Golang数据库Schema迁移嵌入二进制文件插件库Schema的使用

Schema - Go的数据库迁移工具

一个可嵌入的库,用于对Go应用程序的database/sql schema应用变更。

特性

  • 支持集群环境的云友好设计
  • 支持在embed.FS中的迁移(需要Go 1.16+的go:embed)
  • 仅依赖Go标准库
  • 单向迁移(没有"down"迁移的复杂性)

使用说明

创建一个schema.Migrator在你的引导/配置/数据库连接代码中,然后调用它的Apply()方法,传入你的数据库连接和一个*schema.Migration结构体切片。

.Apply()函数会找出哪些提供的迁移尚未在数据库中执行(基于ID),并按ID的字母顺序为每个迁移执行Script

使用go:embed(需要Go 1.16+)

Go 1.16添加了将文件目录作为嵌入式文件系统(embed.FS)嵌入到二进制文件中的功能。

假设你有一个名为my-migrations/的SQL文件目录与你的main.go文件相邻,你可以这样运行:

//go:embed my-migrations
var MyMigrations embed.FS

func main() {
   db, err := sql.Open(...) // 或者你获取*sql.DB的方式

   migrations, err := schema.FSMigrations(MyMigrations, "my-migrations/*.sql")
   migrator := schema.NewMigrator(schema.WithDialect(schema.MySQL))
   err = migrator.Apply(db, migrations)
}

WithDialect()选项接受:schema.MySQLschema.Postgresschema.SQLiteschema.MSSQL

使用内联迁移结构体

如果你运行的是较早版本的Go,需要手动创建Migration{}结构体:

db, err := sql.Open(...)

migrator := schema.NewMigrator() // Postgres是默认的Dialect
migrator.Apply(db, []*schema.Migration{
   &schema.Migration{
      ID: "2019-09-24 Create Albums",
      Script: `
      CREATE TABLE albums (
         id SERIAL PRIMARY KEY,
         title CHARACTER VARYING (255) NOT NULL
      )
      `
   },
})

完整示例Demo

下面是一个完整的示例,展示如何使用schema库进行数据库迁移:

package main

import (
    "database/sql"
    "embed"
    "log"
    
    "github.com/adlio/schema"
    _ "github.com/lib/pq" // PostgreSQL驱动
)

//go:embed migrations/*.sql
var migrationFS embed.FS

func main() {
    // 连接数据库
    db, err := sql.Open("postgres", "user=postgres dbname=mydb sslmode=disable")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // 从嵌入式文件系统加载迁移
    migrations, err := schema.FSMigrations(migrationFS, "migrations/*.sql")
    if err != nil {
        log.Fatal(err)
    }

    // 创建迁移器并应用迁移
    migrator := schema.NewMigrator(
        schema.WithDialect(schema.Postgres),
        schema.WithTableName("my_migrations"),
    )
    
    if err := migrator.Apply(db, migrations); err != nil {
        log.Fatal(err)
    }
    
    log.Println("Migrations applied successfully!")
}

迁移规则

  1. 永远不要更改已经在你数据库上执行的迁移的ID(文件名)或Script(文件内容)。如果你犯了错误,你需要在后续的迁移中纠正它。

  2. 为迁移ID/文件名使用一致的、描述性的格式。考虑用今天的日期作为前缀。例如:

    ID: "2019-01-01T13:45:00 Creates Users"
    ID: "2001-12-18 001 Changes the Default Value of User Affiliate ID"
    

支持的数据库

  • PostgreSQL (仅database/sql驱动)
  • SQLite
  • MySQL / MariaDB
  • SQL Server

包的设计理念

  1. 数据库凭证是运行时配置细节,但数据库schema是构建时应用程序依赖,这意味着它应该被"编译"到构建中,不应该依赖外部工具。
  2. 使用外部命令行工具进行schema迁移不必要地复杂化了测试和部署。
  3. SQL是用于指定SQL schema变更的最佳语言。
  4. "Down"迁移增加了不必要的复杂性,不经常使用,并且在需要使用时很难正确测试。
  5. 应该避免深度依赖链,特别是在编译的二进制文件中。

更多关于golang数据库schema迁移嵌入二进制文件插件库schema的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang数据库schema迁移嵌入二进制文件插件库schema的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang 数据库 Schema 迁移与嵌入二进制文件

在 Go 语言中,处理数据库 schema 迁移并将迁移文件嵌入到二进制中是常见的需求。下面我将介绍几种实现方式。

1. 使用 go-migrate 库

go-migrate 是一个流行的数据库迁移工具,支持多种数据库。

安装

go get -u github.com/golang-migrate/migrate/v4

基本使用

import (
    "github.com/golang-migrate/migrate/v4"
    _ "github.com/golang-migrate/migrate/v4/database/postgres"
    _ "github.com/golang-migrate/migrate/v4/source/file"
)

func main() {
    m, err := migrate.New(
        "file:///path/to/migrations",
        "postgres://user:password@localhost:5432/database?sslmode=disable")
    if err != nil {
        // 处理错误
    }
    
    // 执行所有待处理的迁移
    if err := m.Up(); err != nil && err != migrate.ErrNoChange {
        // 处理错误
    }
}

2. 嵌入迁移文件到二进制

使用 Go 1.16+ 的 embed 功能,我们可以将迁移文件直接嵌入到二进制中。

项目结构

project/
├── migrations/
│   ├── 0001_init.up.sql
│   └── 0001_init.down.sql
├── main.go

使用 embed 嵌入文件

package main

import (
    "embed"
    "github.com/golang-migrate/migrate/v4"
    "github.com/golang-migrate/migrate/v4/source/iofs"
    _ "github.com/golang-migrate/migrate/v4/database/postgres"
)

//go:embed migrations/*.sql
var fs embed.FS

func main() {
    d, err := iofs.New(fs, "migrations")
    if err != nil {
        panic(err)
    }
    
    m, err := migrate.NewWithSourceInstance("iofs", d, "postgres://user:password@localhost:5432/database?sslmode=disable")
    if err != nil {
        panic(err)
    }
    
    if err := m.Up(); err != nil && err != migrate.ErrNoChange {
        panic(err)
    }
}

3. 使用 sql-migrate 库

另一个流行的选择是 sql-migrate。

安装

go get github.com/rubenv/sql-migrate

使用示例

package main

import (
    "database/sql"
    "embed"
    "log"
    
    "github.com/rubenv/sql-migrate"
    _ "github.com/lib/pq"
)

//go:embed migrations/*.sql
var migrationsFS embed.FS

func main() {
    db, err := sql.Open("postgres", "user=postgres dbname=test sslmode=disable")
    if err != nil {
        log.Fatal(err)
    }
    
    // 创建迁移源
    migrations := &migrate.EmbedFileSystemMigrationSource{
        FileSystem: migrationsFS,
        Root:       "migrations",
    }
    
    // 执行迁移
    n, err := migrate.Exec(db, "postgres", migrations, migrate.Up)
    if err != nil {
        log.Fatal(err)
    }
    
    log.Printf("Applied %d migrations!\n", n)
}

4. 自定义简单迁移方案

对于简单需求,可以自己实现迁移逻辑:

package main

import (
    "database/sql"
    "embed"
    "fmt"
    "io/fs"
    _ "github.com/lib/pq"
)

//go:embed migrations/*.sql
var migrations embed.FS

func main() {
    db, err := sql.Open("postgres", "postgres://user:password@localhost/dbname?sslmode=disable")
    if err != nil {
        panic(err)
    }
    defer db.Close()
    
    // 创建迁移表
    _, err = db.Exec(`CREATE TABLE IF NOT EXISTS schema_migrations (
        version TEXT PRIMARY KEY,
        applied_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
    )`)
    if err != nil {
        panic(err)
    }
    
    // 读取已应用的迁移
    applied := make(map[string]bool)
    rows, err := db.Query("SELECT version FROM schema_migrations")
    if err != nil {
        panic(err)
    }
    defer rows.Close()
    
    for rows.Next() {
        var version string
        if err := rows.Scan(&version); err != nil {
            panic(err)
        }
        applied[version] = true
    }
    
    // 应用新迁移
    files, err := fs.ReadDir(migrations, "migrations")
    if err != nil {
        panic(err)
    }
    
    for _, file := range files {
        if file.IsDir() {
            continue
        }
        
        version := file.Name()
        if _, ok := applied[version]; ok {
            continue
        }
        
        content, err := fs.ReadFile(migrations, "migrations/"+version)
        if err != nil {
            panic(err)
        }
        
        // 在事务中执行迁移
        tx, err := db.Begin()
        if err != nil {
            panic(err)
        }
        
        if _, err := tx.Exec(string(content)); err != nil {
            tx.Rollback()
            panic(fmt.Errorf("migration %s failed: %v", version, err))
        }
        
        if _, err := tx.Exec("INSERT INTO schema_migrations (version) VALUES ($1)", version); err != nil {
            tx.Rollback()
            panic(fmt.Errorf("failed to record migration %s: %v", version, err))
        }
        
        if err := tx.Commit(); err != nil {
            panic(err)
        }
        
        fmt.Printf("Applied migration: %s\n", version)
    }
}

最佳实践

  1. 版本控制:每个迁移文件应该有明确的版本号前缀
  2. 原子性:每个迁移应该在一个事务中执行
  3. 回滚:为每个迁移提供对应的回滚脚本
  4. 测试:在测试环境中验证迁移
  5. 备份:在生产环境执行前备份数据

以上方案可以根据项目需求选择使用。对于大多数项目,go-migrate 或 sql-migrate 结合 embed 功能是很好的选择。

回到顶部