当前位置:首页 > 文章列表 > Golang > Go教程 > 数据库迁移对于 Golang 服务,为什么重要?

数据库迁移对于 Golang 服务,为什么重要?

来源:dev.to 2024-10-25 20:18:58 0浏览 收藏

最近发现不少小伙伴都对Golang很感兴趣,所以今天继续给大家介绍Golang相关的知识,本文《数据库迁移对于 Golang 服务,为什么重要?》主要内容涉及到等等知识点,希望能帮到你!当然如果阅读本文时存在不同想法,可以在评论中表达,但是请勿使用过激的措辞~

数据库迁移对于 Golang 服务,为什么重要?

数据库迁移,为什么重要?

您是否曾经遇到过这样的情况:当您使用更新的数据库架构在生产环境中部署新的更新时,但之后出现错误并需要恢复内容......这就是迁移出现的情况。

数据库迁移有几个关键目的:

  1. 架构演变:随着应用程序的演变,它们的数据模型也会发生变化。迁移允许开发人员系统地更新数据库架构以反映这些更改,确保数据库结构与应用程序代码匹配。
  2. 版本控制:迁移提供了一种对数据库架构进行版本控制的方法,允许团队跟踪一段时间内的更改。此版本控制有助于理解数据库的演变并有助于开发人员之间的协作。
  3. 跨环境的一致性:迁移确保数据库架构在不同环境(开发、测试、生产)中保持一致。这降低了可能导致错误和集成问题的差异风险。
  4. 回滚功能:许多迁移工具都支持回滚更改,允许开发人员在迁移导致问题时恢复到数据库之前的状态。这增强了开发和部署过程中的稳定性。
  5. 自动部署:迁移可以作为部署过程的一部分实现自动化,确保将必要的架构更改应用于数据库,而无需手动干预。这简化了发布流程并减少了人为错误。

在golang项目中应用

要使用 gorm 和 mysql 为 golang 服务创建全面的生产级设置,以便轻松迁移、更新和回滚,您需要包含迁移工具、处理数据库连接池并确保正确的结构定义。这是一个完整的示例来指导您完成整个过程:

项目结构

/golang-service
|-- main.go
|-- database
|   |-- migration.go
|-- models
|   |-- user.go
|-- config
|   |-- config.go
|-- migrations
|   |-- ...
|-- go.mod

1.数据库配置(config/config.go)

package config

import (
    "fmt"
    "log"
    "os"
    "time"

    "github.com/joho/godotenv"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

var db *gorm.db

func connectdb() {
    err := godotenv.load()
    if err != nil {
        log.fatal("error loading .env file")
    }

    // charset=utf8mb4: sets the character set to utf8mb4, which supports all unicode characters, including emojis.
    // parsetime=true: tells the driver to automatically parse date and datetime values into go's time.time type.
    // loc=local: uses the local timezone of the server for time-related queries and storage.
    dsn := fmt.sprintf(
        "%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parsetime=true&loc=local",
        os.getenv("db_user"),
        os.getenv("db_pass"),
        os.getenv("db_host"),
        os.getenv("db_port"),
        os.getenv("db_name"),
    )

    db, err := gorm.open(mysql.open(dsn), &gorm.config{})
    if err != nil {
        panic("failed to connect database")
    }

    sqldb, err := db.db()
    if err != nil {
        panic("failed to configure database connection")
    }

    // set connection pool settings
    sqldb.setmaxidleconns(10)
    sqldb.setmaxopenconns(100)
    sqldb.setconnmaxlifetime(time.hour)

    // 1.sqldb.setmaxidleconns(10)
    // sets the maximum number of idle (unused but open) connections in the connection pool.
    // a value of 10 means up to 10 connections can remain idle, ready to be reused.

    // 2. sqldb.setmaxopenconns(100):
    // sets the maximum number of open (active or idle) connections that can be created to the database.
    // a value of 100 limits the total number of connections, helping to prevent overloading the database.

    // 3. sqldb.setconnmaxlifetime(time.hour):
    // sets the maximum amount of time a connection can be reused before it’s closed.
    // a value of time.hour means that each connection will be kept for up to 1 hour, after which it will be discarded and a new connection will be created if needed.

    db = db
}

2. 数据库迁移(database/migration.go)

package database

import (
    "golang-service/models"
    "golang-service/migrations"
    "gorm.io/gorm"
)

func migrate(db *gorm.db) {
    db.automigrate(&models.user{})
    // apply additional custom migrations if needed
}

3.模型(models/user.go)

package models

import "gorm.io/gorm"

type user struct {
    gorm.model
    name  string `json:"name"`
}

4.环境配置(.env)

db_user=root
db_pass=yourpassword
db_host=127.0.0.1
db_port=3306
db_name=yourdb

5. 主入口点(main.go)

package main

import (
    "golang-service/config"
    "golang-service/database"
    "golang-service/models"
    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
)

func main() {
    config.connectdb()
    database.migrate(config.db)

    r := gin.default()
    r.post("/users", createuser)
    r.get("/users/:id", getuser)
    r.run(":8080")
}

func createuser(c *gin.context) {
    var user models.user
    if err := c.shouldbindjson(&user); err != nil {
        c.json(400, gin.h{"error": err.error()})
        return
    }

    if err := config.db.create(&user).error; err != nil {
        c.json(500, gin.h{"error": err.error()})
        return
    }

    c.json(201, user)
}

func getuser(c *gin.context) {
    id := c.param("id")
    var user models.user

    if err := config.db.first(&user, id).error; err != nil {
        c.json(404, gin.h{"error": "user not found"})
        return
    }

    c.json(200, user)
}

6、说明:

  • 数据库配置:管理连接池以获得生产级性能。
  • 迁移文件:(在迁移文件夹中)帮助对数据库架构进行版本控制。
  • gorm 模型:将数据库表映射到 go 结构。
  • 数据库迁移:(在数据库文件夹中)随着时间的推移更改表的自定义逻辑,以便轻松回滚。
  • 测试:您可以使用 httptest 和 testify 为此设置创建集成测试。

7. 创建第一个迁移

  1. 对于生产环境,我们可以使用像 golang-migrate 这样的迁移库来应用、回滚或重做迁移。

    安装golang-migrate

    go install -tags 'mysql' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
    
  2. 为用户表生成迁移文件

    migrate create -ext=sql -dir=./migrations -seq create_users_table
    

    运行命令后,我们将得到一对 .up.sql (用于更新架构)和 down.sql (用于稍后可能的回滚)。数字000001是自动生成的迁移索引。

    /golang-service
    |-- migrations
    |   |-- 000001_create_users_table.down.sql
    |   |-- 000001_create_users_table.up.sql
    

    添加相关sql命令到.up文件和.down文件。

    000001_create_users_table.up.sql

    create table users (
    id bigint auto_increment primary key,
    name varchar(255) not null,
    created_at datetime,
    updated_at datetime,
    deleted_at datetime);
    

    000001_create_users_table.down.sql

    drop table if exists users;
    

    运行向上迁移并使用以下命令将更改应用到数据库(-verbose 标志以查看更多日志详细信息):

    migrate -path ./migrations -database "mysql://user:password@tcp(localhost:3306)/dbname" -verbose up
    

    如果我们遇到迁移问题,我们可以使用以下命令查看当前的迁移版本及其状态:

    migrate -path ./migrations -database "mysql://user:password@tcp(localhost:3306)/dbname" version
    

    如果由于某些原因导致迁移失败,我们可以考虑使用带有脏迁移版本号的强制(谨慎使用)命令。如果版本是1(可以在migrations或schema_migrations表中检查),我们将运行:

    migrate -path ./migrations -database "mysql://user:password@tcp(localhost:3306)/dbname" force 1
    

8.改变计划

  1. 在某个时间点,我们可能想添加新功能,其中一些可能需要更改数据方案,例如我们想向用户表添加电子邮件字段。我们将按照以下方式进行。

    进行新的迁移,将电子邮件列添加到用户表

    migrate create -ext=sql -dir=./migrations -seq add_email_to_users
    

    现在我们有了一对新的 .up.sql 和 .down.sql

    /golang-service
    |-- migrations
    |   |-- 000001_create_users_table.down.sql
    |   |-- 000001_create_users_table.up.sql
    |   |-- 000002_add_email_to_users.down.sql
    |   |-- 000002_add_email_to_users.up.sql
    
  2. 将以下内容添加到*_add_email_to_users.*.sql文件

    000002_add_email_to_users.up.sql

    alter table `users` add column `email` varchar(255) unique;
    

    000002_add_email_to_users.down.sql

    alter table `users` drop column `email`;
    

    再次运行向上迁移命令以更新数据模式

    migrate -path ./migrations -database "mysql://user:password@tcp(localhost:3306)/dbname" -verbose up
    

    我们还需要更新 golang users 结构(添加 email 字段)以使其与新模式保持同步..

    type user struct {
     gorm.model
     name  string `json:"name"`
     email string json:"email" gorm:"uniqueindex"
    }
    

9. 回滚迁移:

如果由于某些原因我们在新更新的模式中出现错误,并且需要回滚,这种情况下我们将使用 down 命令:

migrate -path ./migrations -database "mysql://user:password@tcp(localhost:3306)/dbname" down 1

数字 1 表示我们要回滚 1 个迁移。

这里我们还需要手动更新 golang users 结构(删除 email 字段)以反映数据架构更改。

   type user struct {
    gorm.model
    name  string `json:"name"`
   }

10. 与 makefile 一起使用

为了简化迁移和回滚的过程,我们可以添加一个makefile。

/golang-service
|-- ...
|-- makefile

makefile 内容如下。

include .env

create_migration:
    migrate create -ext=sql -dir=./migrations -seq create_new_migration

migrate_up:
    migrate -path=./migrations -database "mysql://${db_user}:${db_pass}@tcp(${db_host}:${db_port})/${db_name}" -verbose up 1

migrate_down:
    migrate -path=./migrations -database "mysql://${db_user}:${db_pass}@tcp(${db_host}:${db_port})/${db_name}" -verbose down 1

.phony:  create_migration migrate_up migrate_down

现在我们只需在 cli 上运行 make migrate_up 或 make migrate_down 即可进行迁移和回滚。

11.注意事项:

  • 回滚期间数据丢失:回滚删除列或表的迁移可能会导致数据丢失,因此在运行回滚之前始终备份数据。
  • ci/cd 集成:将迁移过程集成到 ci/cd 管道中,以在部署期间自动执行架构更改。
  • 数据库备份:安排定期数据库备份,以防止迁移错误时丢失数据。

关于数据库备份

在回滚迁移或进行可能影响数据库的更改之前,以下是需要考虑的一些关键点。

  1. 架构更改:如果迁移涉及更改架构(例如,添加或删除列、更改数据类型),则回滚到之前的迁移可能会导致存储在这些更改的列或表中的任何数据丢失.
  2. 数据删除:如果迁移包含删除数据的命令(例如删除表或截断表),则回滚将执行相应的“向下”迁移,这可能会永久删除该数据。
  3. 事务处理:如果您的迁移工具支持事务,则回滚可能会更安全,因为更改是在事务中应用的。但是,如果您在事务之外手动运行 sql 命令,则存在丢失数据的风险。
  4. 数据完整性:如果您以取决于当前架构的方式修改了数据,则回滚可能会使您的数据库处于不一致的状态。

所以备份数据至关重要。这是一个简短的指南:

  1. 数据库转储:
    使用特定于数据库的工具创建数据库的完整备份。对于 mysql,您可以使用:

     mysqldump -u root -p dbname > backup_before_rollback.sql
    

    这将创建一个文件 (backup_before_rollback.sql),其中包含 dbname 数据库的所有数据和架构。

  2. 导出特定表:
    如果您只需要备份某些表,请在 mysqldump 命令中指定它们:

    mysqldump -u root -p golang_1 users > users_table_backup.sql
    
  3. 验证备份:
    确保备份文件已创建并检查其大小或打开它以确保它包含必要的数据。

  4. 安全地存储备份:
    将备份副本保存在安全位置,例如云存储或单独的服务器,以防止回滚过程中数据丢失。

云端备份

要在使用 golang 并部署在 aws eks 上时备份 mysql 数据,您可以按照以下步骤操作:

  1. 使用mysqldump进行数据库备份:
    使用 kubernetes cron 作业创建 mysql 数据库的 mysqldump。

    mysqldump -h  -u  -p  > backup.sql
    

    将其存储在持久卷或 s3 存储桶中。

  2. 使用 kubernetes cronjob 实现自动化:
    使用 kubernetes cronjob 自动化 mysqldump 过程。
    yaml 配置示例:yaml

    apiVersion: batch/v1
    kind: CronJob
    metadata:
    name: mysql-backup
    spec:
       schedule: "0 2 * * *" # Runs every day at 2 AM
       jobTemplate:
         spec:
           template:
             spec:
               containers:
                 - name: mysql-backup
                   image: mysql:5.7
                   args:
                     - /bin/sh
                     - -c
                     - "mysqldump -h  -u  -p  | aws s3 cp - s3:///backup-$(date +\\%F).sql"
                   env:
                     - name: AWS_ACCESS_KEY_ID
                       value: ""
                     - name: AWS_SECRET_ACCESS_KEY
                       value: ""
               restartPolicy: OnFailure
    


    `

  3. 使用 aws rds 自动备份(如果使用 rds):
    如果您的 mysql 数据库位于 aws rds 上,您可以利用 rds 自动备份和快照。
    设置备份保留期并手动拍摄快照或使用 lambda 函数自动拍摄快照。

  4. 使用 velero 备份持久卷 (pv):
    使用 kubernetes 备份工具 velero 来备份保存 mysql 数据的持久卷。
    在您的 eks 集群上安装 velero 并将其配置为备份到 s3。

通过使用这些方法,您可以确保您的 mysql 数据得到定期备份和安全存储。

理论要掌握,实操不能落!以上关于《数据库迁移对于 Golang 服务,为什么重要?》的详细介绍,大家都掌握了吧!如果想要继续提升自己的能力,那么就来关注golang学习网公众号吧!

版本声明
本文转载于:dev.to 如有侵犯,请联系study_golang@163.com删除
自定义函数处理事件和回调自定义函数处理事件和回调
上一篇
自定义函数处理事件和回调
Golang框架如何解决缓存问题?
下一篇
Golang框架如何解决缓存问题?
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ljg-skills -
    ljg-skills
    ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
    1731次使用
  • MELO音乐 - AI 音乐生成平台,支持多模态创作能力
    MELO音乐
    MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
    1670次使用
  • UniScribe - AI 免费在线音视频转文字平台
    UniScribe
    UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
    1602次使用
  • 剧云 - 免费 AI 智能中文剧本创作平台
    剧云
    剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
    1805次使用
  • 万象有声 - AI 一站式有声内容创作平台
    万象有声
    万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
    1789次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码