利用go-migrate实现MySQL和ClickHouse的数据库迁移
1. 背景
在使用gorm时 , 尽管已经有了自动建表和钩子函数 . 但是在面临希望了解到数据库的变更 , 和插入一些系统字段时 , 以及最关键的数据库迁移的工作 . gorm显得稍微有点不便 .
在了解到migrate这项技术后 , 就使用go-migrate开发了一个可以迁移MySQL和ClickHouse数据库的工具.
2. 实现
2.1 简单介绍
go-migrate在启动后 , 会在数据库中自动生成一张 "schema_migrations"表 , 这张表在mysql和clickhouse中的结构有一定区别.但是主要的字段是相同的.

clickhouse
"version": 表示版本号
"dirty": 表示执行成功或失败 0:成功 1:失败
2.2 具体实现

先新建一个目录 , 结构可以自己去梳理:
mysql文件夹中存放的是 mysql数据库相关变迁的sql语句
clickhouse文件夹存放的是clickhouse数据库相关变迁的sql语句
migrate.txt只是为了开发人员更好了解到当前执行到什么版本了
所有的sql文件前需要一个版本号,保证是唯一的.
.up: 表示的是需要执行的sql
如果希望自动回滚 , 可以在每一个版本的sql文件后 , 在新建一个sql文件. 且 .up 替换为 .down. 即可自动回滚.
# migrate.txt
1_waf_top_mysql_create_app_waf_table.up.sql
2_waf_top_mysql_create_server_waf_table.up.sql
3_waf_top_mysql_create_waf_allow_list_table.up.sql
4_waf_top_mysql_create_waf_buildin_rule_table.up.sql
5_waf_top_mysql_create_waf_rule_group_table.up.sql
6_waf_top_mysql_create_waf_server_allow_table.up.sql
7_waf_top_mysql_create_waf_servers_strategies_table.up.sql
8_waf_top_mysql_create_waf_strategy_table.up.sql
9_waf_top_mysql_create_waf_strategy_config_table.up.sql
10_waf_top_mysql_create_waf_user_rule_table.up.sql
11_waf_user_mysql_create_waf_user_info_table.up.sql
12_dash_borad_ck_create_sec_log_table.up.sql
13_waf_top_mysql_insert_buildin_rule_data.up.sql
14_waf_top_mysql_insert_waf_rule_group_data.up.sql
15_waf_top_mysql_alter_server_waf_desc.up.sql
前缀数字表示的就是版本号
package migrateimport ("context""database/sql""errors""fmt""path/filepath""time"_ "github.com/ClickHouse/clickhouse-go/v2""github.com/go-redis/redis/v8""github.com/golang-migrate/migrate/v4""github.com/golang-migrate/migrate/v4/database"chMigrate "github.com/golang-migrate/migrate/v4/database/clickhouse"mysqlMigrate "github.com/golang-migrate/migrate/v4/database/mysql"_ "github.com/golang-migrate/migrate/v4/source/file""github.com/google/uuid""github.com/sirupsen/logrus""wafconsole/utils/redislock"_ "github.com/go-sql-driver/mysql"
)// Config 迁移配置
type Config struct {AppName stringMySqlDSN stringClickHouseDSN stringRedisAddr stringRedisPassword stringRedisDB intMigrationDir string // 指向 migrations 父目录(包含 mysql 和 clickhouse 子目录)LockTimeout time.DurationTargetVersion uint
}// DatabaseMigrator 数据库迁移器
type DatabaseMigrator struct {mysqlDB *sql.DBclickhouseDB *sql.DBredisClient *redis.Clientconfig *ConfiglockID string
}// NewDatabaseMigrator 创建新实例
func NewDatabaseMigrator(cfg *Config) (*DatabaseMigrator, error) {// 初始化MySQL连接mysqlDB, err := sql.Open("mysql", cfg.MySqlDSN)if err != nil {return nil, fmt.Errorf("failed to connect to MySQL: %w", err)}// 验证MySQL连接if err = mysqlDB.Ping(); err != nil {return nil, fmt.Errorf("MySQL ping failed: %w", err)}// 初始化ClickHouse连接clickhouseDB, err := sql.Open("clickhouse", cfg.ClickHouseDSN)if err != nil {return nil, fmt.Errorf("failed to connect to ClickHouse: %w", err)}// 验证ClickHouse连接if err = clickhouseDB.Ping(); err != nil {return nil, fmt.Errorf("ClickHouse ping failed: %w", err)}// 初始化Redis客户端rdb := redis.NewClient(&redis.Options{Addr: cfg.RedisAddr,Password: cfg.RedisPassword,DB: cfg.RedisDB,})// 验证Redis连接if err = rdb.Ping(context.Background()).Err(); err != nil {return nil, fmt.Errorf("redis connection failed: %w", err)}return &DatabaseMigrator{mysqlDB: mysqlDB,clickhouseDB: clickhouseDB,redisClient: rdb,config: cfg,lockID: uuid.New().String(),}, nil
}// Run 执行全量迁移
func (m *DatabaseMigrator) Run(ctx context.Context) error {lockKey := "database_migration_lock"rdLock := redislock.NewRedisLock(m.redisClient, m.config.LockTimeout)if err := rdLock.AcquireLock(ctx, lockKey); err != nil {return fmt.Errorf("failed to acquire lock: %w", err)}defer func() {if err := rdLock.ReleaseLock(ctx, lockKey); err != nil {logrus.Errorf("Failed to release lock: %v", err)}}()if err := m.migrateMySQL(ctx); err != nil {return fmt.Errorf("MySQL migration failed: %w", err)}if err := m.migrateClickHouse(ctx); err != nil {return fmt.Errorf("ClickHouse migration failed: %w", err)}return nil
}// MySQL 迁移
func (m *DatabaseMigrator) migrateMySQL(ctx context.Context) error {driver, err := mysqlMigrate.WithInstance(m.mysqlDB, &mysqlMigrate.Config{})if err != nil {return fmt.Errorf("failed to create MySQL driver: %w", err)}return m.runMigration(ctx, driver, "mysql")
}// ClickHouse 迁移
func (m *DatabaseMigrator) migrateClickHouse(ctx context.Context) error {driver, err := chMigrate.WithInstance(m.clickhouseDB, &chMigrate.Config{})if err != nil {return fmt.Errorf("failed to create ClickHouse driver: %w", err)}return m.runMigration(ctx, driver, "clickhouse")
}// 通用迁移逻辑
func (m *DatabaseMigrator) runMigration(ctx context.Context,driver database.Driver,dbType string,
) error {// 获取原始路径migratePath := filepath.Join(m.config.MigrationDir, dbType)// 强制转换为 URL 兼容的斜杠格式migratePath = filepath.ToSlash(migratePath)// 构建 URLsourceURL := fmt.Sprintf("file://%s", migratePath)// 初始化迁移实例migrator, err := migrate.NewWithDatabaseInstance(sourceURL, dbType, driver)if err != nil {return fmt.Errorf("failed to initialize migrator: %w", err)}defer migrator.Close()// 执行迁移var migrationErr errorif m.config.TargetVersion > 0 {migrationErr = migrator.Migrate(m.config.TargetVersion)} else {migrationErr = migrator.Up()}// 处理迁移结果if migrationErr != nil && !errors.Is(migrationErr, migrate.ErrNoChange) {return fmt.Errorf("migration failed: %w", migrationErr)}logrus.Infof("%s migration completed successfully", dbType)return nil
}// Close 关闭资源(保持不变)
func (m *DatabaseMigrator) Close() error {var errs []errorif err := m.mysqlDB.Close(); err != nil {errs = append(errs, fmt.Errorf("MySQL close error: %w", err))}if err := m.clickhouseDB.Close(); err != nil {errs = append(errs, fmt.Errorf("ClickHouse close error: %w", err))}if err := m.redisClient.Close(); err != nil {errs = append(errs, fmt.Errorf("Redis close error: %w", err))}if len(errs) > 0 {return fmt.Errorf("errors occurred during shutdown: %v", errs)}return nil
}
2.3 优化方式
2.3.1 脏版本处理
1. 在执行过程中 , 可能会出现一些因sql语句错误而执行失败 . migrate实现了清洗脏版本的功能.加在通用迁移逻辑 初始化迁移实列后即可.
// 检查是否为脏版本version, dirty, err := migrator.Version()if err != nil && !errors.Is(err, migrate.ErrNilVersion) {return fmt.Errorf("failed to check version: %w", err)}if dirty {// 强制清除脏状态if err = migrator.Force(int(version)); err != nil {return fmt.Errorf("failed to force clean version: %w", err)}}
2. 或者手动修改 表中的版本号 , 修改到上一个版本(即sql文件最开始的数字) , 且状态改为0. 因为当migrate检测到为1执行失败后 , 就不在继续执行了.
2.3.2 分布式锁防止并发情况下 , 同时执行多个迁移操作
lockKey := "database_migration_lock"rdLock := redislock.NewRedisLock(m.redisClient, m.config.LockTimeout)if err := rdLock.AcquireLock(ctx, lockKey); err != nil {return fmt.Errorf("failed to acquire lock: %w", err)}defer func() {if err := rdLock.ReleaseLock(ctx, lockKey); err != nil {logrus.Errorf("Failed to release lock: %v", err)}}()
上面具体实现中已经包含了这段代码 , 是我自己封装的一个redis分布式锁的实现. 这段代码 , 如果不需要可以删除 , 如有需要 , 可以自己实现一个简单的redis分布式锁即可.
相关文章:
利用go-migrate实现MySQL和ClickHouse的数据库迁移
1. 背景 在使用gorm时 , 尽管已经有了自动建表和钩子函数 . 但是在面临希望了解到数据库的变更 , 和插入一些系统字段时 , 以及最关键的数据库迁移的工作 . gorm显得稍微有点不便 . 在了解到migrate这项技术后 , 就使用go-migrate开发了一个可以迁移MySQL和ClickHouse数据库的…...
计算机毕业设计SpringBoot+Vue.js企业客户管理系统(源码+LW文档+PPT+讲解+开题报告)
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
jmeter 如何做移动端的测试 特别是兼容性测试
JMeter本身主要是一款用于性能测试和功能测试的工具,虽然它并非专门为移动端测试设计,但可以通过一些方式来对移动端应用进行测试,以下从测试准备、测试过程及注意事项等方面为你详细介绍: 一、测试准备 (一)环境搭建 JMeter安装与配置:确保JMeter已经正确安装在测试机…...
深度学习技术全景图:从基础架构到工业落地的超级进化指南
🔍 目录导航 基础架构革命训练优化秘技未来战场前瞻 🧩 一、基础架构革命 1.1 前馈神经网络(FNN) ▍核心结构 import torch.nn as nnclass FNN(nn.Module):def __init__(self):super().__init__()self.fc1 nn.Linear(784, 25…...
vllm部署LLM(qwen2.5,llama,deepseek)
目录 环境 qwen2.5-1.5b-instruct 模型下载 vllm 安装 验证安装 vllm 启动 查看当前模型列表 OpenAI Completions API(文本生成) OpenAI Chat Completions API(chat 对话) vllm 进程查看,kill llama3 deep…...
基于SpringBoot的“古城景区管理系统”的设计与实现(源码+数据库+文档+PPT)
基于SpringBoot的“古城景区管理系统”的设计与实现(源码数据库文档PPT) 开发语言:Java 数据库:MySQL 技术:SpringBoot 工具:IDEA/Ecilpse、Navicat、Maven 系统展示 系统整体功能图 系统首页界面 系统注册界面 景…...
如何防止 Docker 注入了恶意脚本
根据您的描述,攻击者通过 CentOS 7 系统中的 Docker 注入了恶意脚本,导致自动启动名为 “masscan” 和 “x86botnigletjsw” 的进程。这些进程可能用于网络扫描或其他恶意活动。为了解决这一问题,建议您采取以下步骤: 1. 停止并删…...
使用python接入腾讯云DeepSeek
本文主要从提供SSE方式接入DeepSeek,并通过fastapi websocket对外提供接入方法。 参考文档: 腾讯云大模型:https://cloud.tencent.com/document/product/1759/109380 fastAPI官网:https://fastapi.tiangolo.com/ WebSocketManager…...
【MySQL】服务正在启动或停止中,请稍候片刻后再试一次【解决方案】
问题呈现 在使用MySQL的过程中我们可能会遇到以上的情况 解决方法 首先以管理员身份打开命令行窗口,注意是管理员身份,不然无权限访问。输入命令tasklist| findstr "mysql",用于查找mysql的残留进程。这个时候我们就会看到一个…...
测试工程师玩转DeepSeek之Prompt
以下是测试工程师使用DeepSeek的必知必会提示词指南,分为核心场景和高效技巧两大维度: 一、基础操作提示模板 1. 测试用例生成 "作为[金融系统/物联网设备/云服务]测试专家,请为[具体功能模块]设计测试用例,要求࿱…...
【PyTorch】2024保姆级安装教程-Python-(CPU+GPU详细完整版)-
一、准备工作 pytorch需要python3.6及以上的python版本 我是利用Anaconda来管理我的python。可自行安装Anaconda。 Anaconda官网 Free Download | Anaconda 具体Anaconda安装教程可参考 https://blog.csdn.net/weixin_43412762/article/details/129599741?fromshareblogdet…...
精选案例展 | 智己汽车—全栈可观测驱动智能化运营与成本优化
本案例为“观测先锋 2024 可观测平台创新应用案例大赛”精选案例,同时荣获IT168“2024技术卓越奖评选-年度创新解决方案”奖。 项目背景 近年来,中国汽车行业进入转型升级阶段,智能网联技术成为行业发展的核心。车联网、自动驾驶等技术的加速…...
MySQL 使用 `WHERE` 子句时 `COUNT(*)`、`COUNT(1)` 和 `COUNT(column)` 的区别解析
文章目录 1. COUNT() 函数的基本作用2. COUNT(*)、COUNT(1) 和 COUNT(column) 的详细对比2.1 COUNT(*) —— 统计所有符合条件的行2.2 COUNT(1) —— 统计所有符合条件的行2.3 COUNT(column) —— 统计某一列非 NULL 的记录数 3. 性能对比3.1 EXPLAIN 分析 4. 哪种方式更好&…...
Linux运维——网络管理
Linux网络管理 一、Linux网络应用要点二、命令常见用法2.1、curl2.1.1、发送GET请求2.1.2、发送POST请求2.1.3、设置请求头2.1.4、处理cookies2.1.5、处理重定向2.1.6、调试和详细信息2.1.7、使用代理2.1.8、文件上传2.1.9、其它常用选项2.1.10、综合示例 2.2、wget2.2.1、基本…...
STM32CUBEIDE FreeRTOS操作教程(十三):task api 任务访问函数
STM32CUBEIDE FreeRTOS操作教程(十三):task api 任务访问函数 STM32CUBE开发环境集成了STM32 HAL库进行FreeRTOS配置和开发的组件,不需要用户自己进行FreeRTOS的移植。这里介绍最简化的用户操作类应用教程。以STM32F401RCT6开发板…...
Jmeter+Jenkins接口压力测试持续集成
项目介绍 接口功能测试应用: http://www.weather.com.cn/data/cityinfo/<city_code>.html 测试功能:获取对应城市的天气预报 请求方法:Get 压测脚本开发工具:jmeter 源码脚本位置: https://github.com/shife…...
深入浅出ES6:现代JavaScript的基石
ES6(ECMAScript 2015)是JavaScript语言的一次重大更新,引入了许多新特性,使JavaScript更加强大、优雅和易于维护。这些特性已经成为现代JavaScript开发的基石,掌握它们对于任何JavaScript开发者都至关重要。本文将深入…...
实现使用RBF(径向基函数)神经网络模拟二阶电机数学模型中的非线性干扰,以及使用WNN(小波神经网络)预测模型中的非线性函数来抵消迟滞影响的功能
下面将详细介绍如何实现使用RBF(径向基函数)神经网络模拟二阶电机数学模型中的非线性干扰,以及使用WNN(小波神经网络)预测模型中的非线性函数来抵消迟滞影响的功能。我们将按照以下步骤进行: 步骤1&#x…...
潜水泵,高效排水,守护城市与农田|深圳鼎跃
洪水是常见的自然灾害,在春夏季节的我国降水多为丰富,容易造成城市内部的洪涝灾害。特别是低洼地区的积水,不仅容易造成城市交通的出行不便,还存在潜在的隐患,严重影响了人们正常生活。 潜水泵作为一种高效、可靠的排水…...
易基因:RNA甲基化修饰和R-loop的交叉调控:从分子机制到临床意义|深度综述
大家好,这里是专注表观组学十余年,领跑多组学科研服务的易基因。 R-loop(RNA-DNA杂合结构)是转录调控、DNA复制和修复等关键细胞过程的重要组成部分。但R-loop异常积累可能会破坏基因组完整性,从而导致多种疾病的发生…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...
AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...
