Golang实践录:一个数据库迁移的代码记录 Apache Derby:一款基于 Java 的嵌入式关系型数据库 centos7 安装 jdk8 (复制粘贴系列) 堡垒机Jumpserver部署 linux-linux常用命令总结二&&Linux其他网络知识&&远程拷贝以&&远程登录服务器 Linux应用程序开发笔记:配置linuxptp开机启动(ubuntu gPTP) Janus WebRTC服务器部署 【Nginx】nginx 的三种反向代理方式 OpenLDAP源码安装及配置管理 ceph客户端安装配置访问rbd 把编译好的ko文件加载模块时出错:Error: could not insert module hello_world.ko: Invalid module format 如何安装pkg-config docker文件存放路径, 获取容器启动命令 修炼js 7 es6新特性2 网页三栏布局常用方法 wordpress必须禁用REST API和移除WP-JSON链接的方法 面试官让我用纯css做一个下拉菜单,一分钟搞定!! CSS常用样式(二):绘制双箭头 住建部重申“房子是用来住的”;商务部公布禁塑时间表;在线旅游网站不得大数据杀熟 给大数据杀熟戴上“紧箍咒” RT-Thread推物联网操作系统!300秒启动安防摄像机 如何用一句话激怒程序员! 吉利在青岛成立物联网、卫星技术新公司 统计学和大数据之间有哪些联系 图说:九月开门红 大数据揭示A股九月行情走向 物联网时代下的“芯机遇”,「芯来科技」打造国产RISC-V生态 汇顶科技:指纹芯片巨头半年研发投入增八成,拓展物联网、汽车电子等领域应对风险 新华网|福州:树立物联网产业“马尾坐标” 「行走自贸区」福州:树立物联网产业“马尾坐标” k8s跨namespace访问服务 Mysql关键字和保留字(注意) ALTER command denied to user 'dev'@'192.168.10.xxx' for table 'xx_fanv_fail' oracle授权查询 ffmpeg vsync参数分析 小程序云函数中用group分组查询,只能查询20条,怎么解决? 【MyBatis&MyBatis-plus】分页查询避免两次查询同时返回总数和数据 数据库执行update语句没有反应,一直转圈,数据库锁表处理解决方法 按键精灵-自定义函数time(),datetime(),diff() 在Vue+Element中,Select选项值动态从后台接口获取的实现方法 mongoTemplate的方法和update() 欧科云链OKLink推出“链上天眼”功能 区块链+大数据全面助力链上安全 第二届微热点大数据论坛将于9月5日开幕 网龙参与全国教育大数据统计 构建五级服务支撑体系 无代码app制作崛起,app开发已不再是程序员的专利了 torch深入学习之凤飞九天torch.nn.MarginRankingLoss 【机器学习】—各类梯度下降算法 简要介绍 隐马尔可夫模型(HMM)来龙去脉(二) 单智能体、多智能体强化学习基本概念及算法分类?为啥提出多智能体强化学习,现状? 张量网络算法基础(八、张量网络机器学习【上】) MapReduce 简介
您的位置:首页 >运维 >

Golang实践录:一个数据库迁移的代码记录

实现一个数据库迁移的案子。有些知识点值得记录。

技术框架

github.com/go-xorm/xorm:数据库操作
github.com/denisenkom/go-mssqldb:sqlserver驱动
github.com/go-sql-driver/mysql:mysql驱动

方案设计

使用 sql 语句查询原数据库数据,再插入新数据库。
查询新数据库表最后一条记录。
根据条件是否创建新数据表,再查询新数据库最后一条记录的 ID 值,以此为起点查询旧数据库,因为迁移只需要从已导入的最后一条开始即可,如果表不存在,则从 0 开始。
使用回调函数,获取旧数据库,处理数据(或舍弃,或修改,等),再插入到新数据库,直接用 xorm 的结构体即可。xorm 可批量插入,但是旧数据库无法做到批量获取,查询并存储到切片中,到达一定数量(如3000条),再插入新数据库。

选择记录

原数据库为 sqlserver,表和列部分有中文,不符合 xorm 要求。只能使用 sql 语句操作。
新数据库为 mysql,全英文,可用 xorm 结构体映射,查询、插入较方便。
试过将 xorm 的结构体成员变量改为中文,在 Golang 中使用中文作为变量名是可以的,只是反射不成功。

实践过程及知识点

数据库连接

// mysqlroot:root@tcp(8.8.8.8:3305)/mydb?charset=utf8&interpolateParams=true&parseTime=true&loc=Local// mssqlserver=8.8.8.8;user id=latelee;password=123456;database=mydb;encrypt=disable;

注意,使用github.com/denisenkom/go-mssqldb时,需要添加encrypt=disable;,否则加不上。

xorm小知识

设置时区:

engine.DatabaseTZ = time.UTC// time.Localengine.TZLocation = time.UTC

是否显示 sql 语句:

//engine.ShowSQL(true)engine.ShowSQL(false)

xorm结构体映射

xorm 使用结构体与数据库字段映射,各种操作十分方便。
设置完全映射:

engine.SetMapper(core.SameMapper{})

定义示例:

type TheData struct {Id int`xorm:"int pk not null autoincr "id""` // autoincrMoney sql.NullFloat64 `xorm:"float default null"`}

第一成员带 id,在数据库即为此名,第二成员不带,将映射为 Money。
结构体成员需大写,否则反射失败。

新数据库创建

一般创建表使用其它的方法,但为了方便使用,直接在代码中创建。

var sqlstr string//sqlstr = "show tables like "xxx""sqlstr = fmt.Sprintf("show tables like "%s"", newTableName)_, err = engine.SQL(sqlstr).Count()if err != nil { // 没有找到现成函数判断,用返回值log.Printf("table %v exist\n", newTableName)} else {log.Printf("create table for %v\n", newTableName)err = engine.Sync2(vnewtable)if err != nil {log.Println("create table failed: ", err.Error(), "will exit")return}}

代码先判断是否存在数据表,不存在再调用engine.Sync2创建,该函数可同时创建多个数据表。不过似乎没有直接的 API 接口判断是否存在数据表,只好用 sql 语句查询,判断其返回值。

数据库为空值

如果读取到数据库中的空值,会返回错误:

Scan failed: sql: Scan error on column index 26, name "测试空值": converting driver.Value type string ("NULL") to a float32: invalid syntax

此问题可用 sql 包提供的类型解决。如sql.NullStringsql.NullFloat64等。这些类型实际是结构体,包括了Valid成员,通过该值可判断。

代码重用

由于迁移过程是完全一样的,只是数据库、表名、字段等不同。可以将共用部分抽象成函数,具体操作使用回调函数解决。
至于不同之处,则由调用者将其传递到共用函数中即可。

回调函数定义和使用

type DataCb func (rows *sql.Rows, engine *xorm.Engine, totalCnt int64) intfunc MigrateDB(olddb, oldtable, newdb string, vnewtable interface{}, datacb DataCb) {...datacb(rows, engin, cnt)}

在回调函数中,根据旧数据库字段扫描,示例如:

for rows.Next() {err := rows.Scan(&a, &b, &c, &d)}

注意,Scan 参数个数和数据库的列数相同,否则出错。可直接用新数据库结构体成员,如果不需要,可以使用其它变量。

结构体映射

结构体用指针传递,这样可进行通用处理,即 MigrateDB 不再与具体结构体关联。

// 获取新表结构体名称atype := reflect.TypeOf(vnewtable)if atype.Kind() == reflect.Ptr { // 如果是结构体指针,再获取结构体atype = atype.Elem()}// 如果还不是结构体,出错if atype.Kind() != reflect.Struct {log.Println("Check type error not Struct")return}newTableName := atype.Name()// 取第N个字段及值idx := 0var newIDName string = atype.Field(idx).Name// 此处为ID值,如果获取不到tag,选择变量名// 查找单引号,得到tagxormStr := atype.Field(idx).Tag.Get("xorm")idx1 := strings.Index(xormStr, """)idx2 := strings.LastIndex(xormStr, """)if idx1 != -1 && idx2 != -1 && idx1 < idx2 {newIDName = xormStr[idx1+1:idx2]}log.Println("debug new table: ", newTableName, newIDName)

功能有二:获取结构体名称,因为 sql 语句需要使用该名称。获取第 0 个字段名称,结合实际情况,数据库第 0 个字段为 ID,故设计上,将结构体第 0 个字段置为 ID。
注意,这里使用结构体指针形式传递,不需要额外声明结构体变量,代码较整洁。

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。