golang实现MySQL和PostgreSQL多库迁移管理的插件库libschema的使用

Golang实现MySQL和PostgreSQL多库迁移管理的插件库libschema的使用

安装

go get github.com/muir/libschema

库介绍

Libschema为Go库提供了一种管理自己数据库迁移的方式。将迁移与库绑定支持两个功能:一是源代码局部性,迁移可以靠近使用迁移所涉及表的代码;二是支持第三方库中的迁移。

注册和执行迁移

迁移首先被注册:

schema := libschema.New(ctx, libschema.Options{})

sqlDB, err := sql.Open("postgres", "....")

database, err := lspostgres.New(logger, "main-db", schema, sqlDB) 
// 或者
database, singlestoreHelper, err := lssinglestore.New(logger, "main-db", schema, sqlDB)
// 或者
database, mysqlHelper, err := lssinglestore.New(logger, "main-db", schema, sqlDB)

database.Migrations("MyLibrary",
	lspostgres.Script("createUserTable", `
		CREATE TABLE users (
			name	text,
			id	bigint
		)`
	}),
	lspostgres.Script("addLastLogin", `
		ALTER TABLE users
			ADD COLUMN last_login timestamp
		`
	}),
)

然后按注册顺序运行迁移:

err := schema.Migrate(context)

计算迁移

迁移可以是SQL字符串,也可以在Go中完成:

database.Migrations("MyLibrary", 
	lspostgres.Computed("importUsers", func(_ context.Context, _ Migration, tx *sql.Tx) error {
		// 在这里导入用户的代码
	}),
)

异步迁移

迁移的默认模式是在调用schema.Migrate()时同步运行。异步迁移在调用schema.Migrate()时启动,但在后台的goroutine中运行。如果在异步迁移之后有后续迁移,它们将强制异步迁移变为同步,除非它们也是异步的。

版本阻塞

迁移可以与特定的代码版本绑定,以便在满足条件之前不运行。这是通过SkipRemainingIf完成的。可以用于回填数据。

database.Migrations("MyLibrary",
	...
	lspostgres.Script("addColumn", `
			ALTER TABLE users
				ADD COLUMN rating`,
	libschema.SkipThisAndFollowingIf(func() bool {
		return semver.Compare(version(), "3.11.3") < 1
	})),
	lspostgres.Script("fillInRatings", `
			UPDATE	users
			SET	rating = ...
			WHERE	rating IS NULL;

			ALTER TABLE users
				MODIFY COLUMN rating SET NOT NULL;`,
	libschema.Asychronous),
)

跨库依赖

尽管最好一个库的模式独立于另一个库的模式,但有时这是不可能的,特别是如果你想强制执行外键约束。使用After()指定跨库依赖。

database.Migrations("users",
	...
	lspostgres.Script("addOrg", `
			ALTER TABLE users
				ADD COLUMN org TEXT,
				ADD ADD CONSTRAINT orgfk FOREIGN KEY (org)
					REFERENCES org (name) `, 
		libschema.After("orgs", "createOrgTable")),
)

database.Migrations("orgs",
	...
	lspostgres.Script("createOrgTable", `
		...
	`),
)

事务

对于支持元数据事务的数据库,所有迁移都将用BEGINCOMMIT包装。对于不支持元数据事务的数据库,迁移将被拆分为单个命令并逐个运行。如果只有部分命令成功,迁移将被标记为部分完成。如果迁移被修改,只要不修改早期部分,可以重新尝试后面的部分。这不适用于Compute()迁移。

命令行

OverrideOptions可以作为命令行标志添加,改变调用schema.Migrate()的行为:

--migrate-only			在完成迁移后调用os.Exit()
--migrate-database		仅迁移一个逻辑数据库(必须匹配NewDatabase)
--migrate-dsn			覆盖*sql.DB 
--no-migrate			跳过所有迁移
--error-if-migrate-needed	如果有未完成的同步迁移则返回错误
--migrate-all-synchronously	将异步迁移视为同步

完整示例

以下是一个完整的示例,展示如何使用libschema管理MySQL和PostgreSQL的迁移:

package main

import (
	"context"
	"database/sql"
	"log"
	"os"

	"github.com/muir/libschema"
	"github.com/muir/libschema/lsmysql"
	"github.com/muir/libschema/lspostgres"
)

func main() {
	ctx := context.Background()
	logger := log.New(os.Stdout, "", log.LstdFlags)

	// 创建schema实例
	schema := libschema.New(ctx, libschema.Options{
		Logger: logger,
	})

	// PostgreSQL连接
	pgDB, err := sql.Open("postgres", "user=postgres dbname=test sslmode=disable")
	if err != nil {
		log.Fatal(err)
	}

	// MySQL连接
	mysqlDB, err := sql.Open("mysql", "user:password@/test")
	if err != nil {
		log.Fatal(err)
	}

	// 创建PostgreSQL数据库实例
	pgDatabase, err := lspostgres.New(logger, "postgres-db", schema, pgDB)
	if err != nil {
		log.Fatal(err)
	}

	// 创建MySQL数据库实例
	mysqlDatabase, mysqlHelper, err := lsmysql.New(logger, "mysql-db", schema, mysqlDB)
	if err != nil {
		log.Fatal(err)
	}

	// 注册PostgreSQL迁移
	pgDatabase.Migrations("MyApp",
		lspostgres.Script("createUsersTable", `
			CREATE TABLE users (
				id SERIAL PRIMARY KEY,
				name TEXT NOT NULL,
				email TEXT UNIQUE NOT NULL
			)`,
		),
		lspostgres.Script("addUserStatus", `
			ALTER TABLE users
				ADD COLUMN status TEXT DEFAULT 'active'`,
		),
	)

	// 注册MySQL迁移
	mysqlDatabase.Migrations("MyApp",
		lsmysql.Script("createProductsTable", `
			CREATE TABLE products (
				id INT AUTO_INCREMENT PRIMARY KEY,
				name VARCHAR(255) NOT NULL,
				price DECIMAL(10,2) NOT NULL
			)`,
		),
		lsmysql.Computed("importSampleProducts", func(ctx context.Context, _ libschema.Migration, tx *sql.Tx) error {
			_, err := tx.ExecContext(ctx, `
				INSERT INTO products (name, price) VALUES 
				('Product 1', 10.99),
				('Product 2', 20.50),
				('Product 3', 15.75)`)
			return err
		}),
	)

	// 运行所有迁移
	err = schema.Migrate(ctx)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("所有迁移成功完成")

	// 使用mysqlHelper进行MySQL特定操作
	err = mysqlHelper.Ping()
	if err != nil {
		log.Fatal(err)
	}
}

支持的数据库

Libschema需要特定数据库的驱动程序:

  • PostgreSQL支持在"github.com/muir/libschema/lspostgres"
  • MySQL支持在"github.com/muir/libschema/lsmysql"
  • SingleStore支持在"github.com/muir/libschema/lssinglestore"

当前libschema支持:PostgreSQL、SingleStore、MySQL。添加其他数据库相对容易。

总结

Libschema是一个强大的Go库,用于管理MySQL和PostgreSQL等多数据库的迁移。它提供了灵活的迁移定义方式,支持同步和异步迁移,跨库依赖,版本控制等功能。通过将迁移与代码紧密结合,它简化了数据库模式的管理和维护。


更多关于golang实现MySQL和PostgreSQL多库迁移管理的插件库libschema的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang实现MySQL和PostgreSQL多库迁移管理的插件库libschema的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


使用libschema实现MySQL和PostgreSQL多库迁移管理

libschema是一个用于管理数据库schema迁移的Go库,支持多种数据库包括MySQL和PostgreSQL。下面我将介绍如何使用libschema来管理多数据库的迁移。

基本概念

libschema的核心概念包括:

  • Migration:表示一个数据库迁移操作
  • Library:管理迁移的集合
  • Schema:代表一个数据库schema的迁移状态

安装

go get github.com/muir/libschema

基本使用示例

1. 初始化配置

package main

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

	"github.com/muir/libschema"
	"github.com/muir/libschema/postgres"
	"github.com/muir/libschema/mysql"
	_ "github.com/go-sql-driver/mysql"
	_ "github.com/lib/pq"
)

func main() {
	// 创建数据库连接
	pgDB, err := sql.Open("postgres", os.Getenv("POSTGRES_DSN"))
	if err != nil {
		log.Fatalf("PostgreSQL连接失败: %v", err)
	}
	defer pgDB.Close()

	mysqlDB, err := sql.Open("mysql", os.Getenv("MYSQL_DSN"))
	if err != nil {
		log.Fatalf("MySQL连接失败: %v", err)
	}
	defer mysqlDB.Close()

	// 创建libschema实例
	options, err := libschema.OptionsFromEnvironment()
	if err != nil {
		log.Fatalf("配置错误: %v", err)
	}

	schema, err := libschema.New(context.Background(), options)
	if err != nil {
		log.Fatalf("初始化libschema失败: %v", err)
	}

	// 添加PostgreSQL数据库
	pgSchema := postgres.New("myapp", schema, pgDB)

	// 添加MySQL数据库
	mysqlSchema := mysql.New("myapp", schema, mysqlDB)

	// 定义迁移
	err = defineMigrations(pgSchema, mysqlSchema)
	if err != nil {
		log.Fatalf("定义迁移失败: %v", err)
	}

	// 执行迁移
	err = schema.Migrate(context.Background())
	if err != nil {
		log.Fatalf("迁移失败: %v", err)
	}

	fmt.Println("迁移完成!")
}

2. 定义迁移

func defineMigrations(pgSchema *postgres.Schema, mysqlSchema *mysql.Schema) error {
	// PostgreSQL迁移
	_, err := pgSchema.Migrations("DBSchema",
		libschema.Script("create_users_table", `
			CREATE TABLE users (
				id SERIAL PRIMARY KEY,
				name TEXT NOT NULL,
				email TEXT UNIQUE NOT NULL,
				created_at TIMESTAMP DEFAULT NOW()
			);
		`),
		libschema.Script("add_user_status", `
			ALTER TABLE users ADD COLUMN status TEXT DEFAULT 'active';
		`),
	)
	if err != nil {
		return fmt.Errorf("PostgreSQL迁移定义失败: %w", err)
	}

	// MySQL迁移
	_, err = mysqlSchema.Migrations("DBSchema",
		libschema.Script("create_products_table", `
			CREATE TABLE products (
				id INT AUTO_INCREMENT PRIMARY KEY,
				name VARCHAR(255) NOT NULL,
				price DECIMAL(10,2) NOT NULL,
				created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
			);
		`),
		libschema.Script("add_product_category", `
			ALTER TABLE products ADD COLUMN category VARCHAR(100) DEFAULT 'general';
		`),
	)
	if err != nil {
		return fmt.Errorf("MySQL迁移定义失败: %w", err)
	}

	return nil
}

高级功能

1. 条件迁移

// 只在开发环境中执行的迁移
if os.Getenv("ENV") == "development" {
	_, err = pgSchema.Migrations("DevSchema",
		libschema.Script("dev_data", `
			INSERT INTO users (name, email) VALUES 
			('Dev User', 'dev@example.com');
		`),
	)
}

2. Go函数迁移

// 使用Go函数而不是SQL脚本的迁移
_, err = pgSchema.Migrations("Functions",
	libschema.Do("create_hello_function", func(ctx context.Context, db *sql.DB) error {
		_, err := db.ExecContext(ctx, `
			CREATE OR REPLACE FUNCTION hello(name TEXT) 
			RETURNS TEXT AS $$
			BEGIN
				RETURN 'Hello ' || name;
			END;
			$$ LANGUAGE plpgsql;
		`)
		return err
	}),
)

3. 迁移依赖

// 定义有依赖关系的迁移
_, err = pgSchema.Migrations("Dependent",
	libschema.Script("create_posts_table", `
		CREATE TABLE posts (
			id SERIAL PRIMARY KEY,
			user_id INTEGER REFERENCES users(id),
			title TEXT NOT NULL,
			content TEXT,
			created_at TIMESTAMP DEFAULT NOW()
		);
	`, libschema.After("create_users_table")), // 依赖users表的创建
)

最佳实践

  1. 版本控制:将迁移脚本与代码一起纳入版本控制
  2. 命名规范:使用有意义的迁移名称,如"create_users_table"
  3. 原子性:每个迁移应该是原子的,要么完全成功,要么完全失败
  4. 回滚考虑:设计迁移时要考虑如何回滚
  5. 环境分离:区分开发、测试和生产环境的迁移

错误处理

libschema提供了详细的错误信息,建议在生产环境中记录这些错误:

err = schema.Migrate(context.Background())
if err != nil {
	if migrationErr, ok := err.(libschema.MigrationErrors); ok {
		for dbName, dbErr := range migrationErr {
			log.Printf("数据库 %s 迁移失败: %v", dbName, dbErr)
		}
	} else {
		log.Printf("迁移错误: %v", err)
	}
	os.Exit(1)
}

libschema是一个功能强大且灵活的数据库迁移管理工具,特别适合需要在多种数据库上维护schema的项目。通过合理组织迁移脚本和利用其高级功能,可以有效地管理复杂的数据库变更。

回到顶部