golang 实现 mysql 数据库备份

2018年7月5日 260点热度 0人点赞 0条评论

背景

navicat是mysql可视化工具中最棒的,但是,在处理视图的导入导出方面,它是按照视图名称的字母顺序来处理的,若视图存在依赖,在导入过程中就会报错。前面已经用python写了一个,但在使用过程中,遇到\xfffd编码,python的pymysql会直接崩溃。发现golang没有这个问题,正好用go重写,来熟悉golang。

一些关键点

  1. map & json,在处理主键与外键信息时,需要用到json数据结构来存储中间结果,因为要灵活处理,在golang中只能用map[string]interface{}来处理。

  2. interface{} 相当于java中的object,能接受任意数据类型,方便但在使用时要做到心中有数,不然一旦数据类型不匹配,程序就会崩溃。

  3. \xfffd ,utf8中的占位符,超出范围的utf8mb4入库后,会被存储为\xfffd,数据导出时,需要过滤掉。

  4. goroutine, golang的并发支持很独特,我们的工具支持多个库同时备份,很容易使用goroutine来实现并行。

代码解析

按功能模块对核心代码进行说明。

main.go,并发、命令行参数

使用命令行参数,接受一个参数,来指定备份的内容:package common。

  1. type OpFlag struct {

  2.    Tables bool            //表结构

  3.    Datum bool             //表结构和数据

  4.    Views bool             //视图

  5.    Funcs bool             //函数与存储过程

  6. }

main.go,程序入口,处理命令行参数:

  1.    if len(os.Args) > 1 {

  2.        flag = common.OpFlag{

  3.            Tables: false,

  4.            Datum: false,

  5.            Views: false,

  6.            Funcs: false,

  7.        }

  8.        switch os.Args[1] {          //接受一个参数

  9.        case "table":

  10.            flag.Tables = true       //根据参数设定标识量

  11.        case "data":

  12.            flag.Tables = true

  13.            flag.Datum = true

  14.        case "views":

  15.            flag.Views = true

  16.        case "funcs":

  17.            flag.Funcs = true

  18.        default:                    //参数不正确,报错退出

  19.            log.Fatal("You arg must be in : table, data, views or funcs.")

  20.        }

  21.    }else{                        //无参数,默认导出所有

  22.        flag = common.OpFlag{

  23.            Tables: true,

  24.            Datum: true,

  25.            Views: true,

  26.            Funcs: true,

  27.        }

  28.    }

  29.    err := backUp.Export(flag)  根据参数进行数据库备份

Export.go

备份主流程,根据configs.json生成goroutine来备份数据库,并等待完成。

  1. var configs interface{}

  2.    fr, err := os.Open("./configs.json")

  3.    if err != nil {

  4.        return err

  5.    }

  6.    decoder := json.NewDecoder(fr)                 //解析配置文件

  7.    err = decoder.Decode(&configs)

  8.    confs := configs.(map[string]interface{})

  9.    workDir := confs["workDir"].(string)

  10.    ch := make(chan string)                       //通道变量

  11.    for key, value := range confs {

  12.        if strings.HasPrefix(key, "db_") {

  13.            dbConf := value.(map[string]interface{})

  14.            dbConn := common.DbConnFields{      //数据库相应配置

  15.                DbHost:    dbConf["db_host"].(string),

  16.                DbPort:    int(dbConf["db_port"].(float64)),

  17.                DbUser:    dbConf["db_user"].(string),

  18.                DbPass:    dbConf["db_pass"].(string),

  19.                DbName:    dbConf["db_name"].(string),

  20.                DbCharset: dbConf["db_charset"].(string),

  21.            }

  22.            if dbConf["file_alias"] != nil {    //生成sql备份文件的命名

  23.                dbConn.FileAlias = dbConf["file_alias"].(string)

  24.            }

  25.            go ExportOne(dbConn, workDir, ch, flag)  //创建协程

  26.        }

  27.    }

  28.    for key := range confs {                        //阻塞主进程,待所有协程完成工作

  29.        if strings.HasPrefix(key, "db_") {

  30.            fmt.Print( <- ch )

  31.        }

  32.    }

  33.    return nil

你需要编写如下的配置文件来描述你要备份的数据库:

  1. {

  2.    "db_name1": {

  3.        "db_host": "192.168.1.8",

  4.        "db_port": 3306,

  5.        "db_user": "root",

  6.        "db_pass": "123456",

  7.        "db_name": "name1",

  8.        "db_charset": "utf8mb4",

  9.        "file_alias": "file name1"

  10.    },

  11.    "db_name2": {

  12.        "db_host": "localhost",

  13.        "db_port": 3306,

  14.        "db_user": "root",

  15.        "db_pass": "123456",

  16.        "db_name": "name2",

  17.        "db_charset": "utf8mb4"

  18.    },

  19.    "database_dialect": "mysql",

  20.    "workDir": "/home/zhoutk/gocodes/goTools/"

  21. }

ExportOne.go

备份一个数据库:

  1.    fileName := fields.FileAlias

  2.    setSqlHeader(fields, fileName)            //设置导出文件说明

  3.    if flag.Tables {                        //如果表设置为真,导出表结构

  4.        err := exportTables(fileName, fields, flag)   //具体算法请参照源代码

  5.        if err != nil {

  6.            ch <- fmt.Sprintln("Error: ", fields.DbName, "\t export tables throw, \t", err)

  7.            return

  8.        }

  9.    }

  10.    if flag.Views {                       //如果视图设置为真,导出视图

  11.        err := exportViews(fileName, fields)//具体算法请参照源代码,或python算法

  12.        if err != nil {

  13.            ch <- fmt.Sprintln("Error: ", fields.DbName, "\t export views throw, \t", err)

  14.            return

  15.        }

  16.    }

  17.    if flag.Funcs {                //如果函数设置为真,导出函数和存储过程

  18.        err := exportFuncs(fileName, fields)//具体算法请参照源代码

  19.        if err != nil {

  20.            ch <- fmt.Sprintln("Error: ", fields.DbName, "\t export funcs throw, \t", err)

  21.            return

  22.        }

  23.    }

  24.    //导出工作完成,向通道输入信息

  25.    ch <- fmt.Sprintln("Export ", fields.DbName, "\t success at \t", time.Now().Format("2006-01-02 15:04:05"))      

MysqlDao.go

数据库查询通用封装,此工具只使用了ExecuteWithDbConn。灵活的使用map与interface{},将结果转化为键值对象返回。

  1. func ExecuteWithDbConn(sql string, values []interface{}, fields common.DbConnFields) (map[string]interface{}, error)  {

  2.    rs := make(map[string]interface{})

  3.    dao, err := mysql.Open("mysql", fields.DbUser + ":"+fields.DbPass+"@tcp("+fields.DbHost+":"+

  4.        strconv.Itoa(fields.DbPort)+")/"+fields.DbName+"?charset="+fields.DbCharset)

  5.    defer dao.Close()

  6.    if err != nil {

  7.        rs["code"] = 204

  8.        return rs, err

  9.    }

  10.    stmt, err := dao.Prepare(sql)

  11.    if err != nil {

  12.        rs["code"] = 204

  13.        return rs, err

  14.    }

  15.    rows, err := stmt.Query(values...)

  16.    if err != nil {

  17.        rs["code"] = 204

  18.        return rs, err

  19.    }

  20.    columns, err := rows.Columns()       //取出字段名称

  21.    vs := make([]mysql.RawBytes, len(columns))

  22.    scans := make([]interface{}, len(columns))

  23.    for i := range vs {                 //预设取值地址

  24.        scans[i] = &vs[i]

  25.    }

  26.    var result []map[string]interface{}

  27.    for rows.Next() {

  28.        _ = rows.Scan(scans...)        //塡入一列值

  29.        each := make(map[string]interface{})

  30.        for i, col := range vs {

  31.            if col != nil {

  32.                each[columns[i]] = FilterHolder(string(col)) //过滤/xfffd

  33.            }else{

  34.                each[columns[i]] = nil

  35.            }

  36.        }

  37.        result = append(result, each)

  38.    }

  39.    rs["code"] = 200

  40.    //data, _ := json.Marshal(result)

  41.    rs["rows"] = result

  42.    return rs, err

  43. }

项目地址

https://github.com/zhoutk/goTools

使用方法

  1. git clone https://github.com/zhoutk/goTools

  2. cd goTools

  3. go get

  4. go run main.go

  5. go buid main.go

  6. ./main                  #export all things of database

  7. ./main table            #export tables

  8. ./main data             #export tables & data

  9. ./main views            #export views

  10. ./main funcs            #export funcs & stored procedures

小结

熟悉了golang语言,了解了一种全新的并发编程模式,给自己生成了一个方便的工具。


欢迎关注 SegmentFault 微信公众号 :)

图片

21380golang 实现 mysql 数据库备份

root

这个人很懒,什么都没留下

文章评论