go 获得 mysql 实际运行 SQL,Golang实践录:一个数据库迁移的代码记录
實(shí)現(xiàn)一個(gè)數(shù)據(jù)庫(kù)遷移的案子。有些知識(shí)點(diǎn)值得記錄。
技術(shù)框架
github.com/go-xorm/xorm:數(shù)據(jù)庫(kù)操作
github.com/denisenkom/go-mssqldb:sqlserver驅(qū)動(dòng)
github.com/go-sql-driver/mysql:mysql驅(qū)動(dòng)
方案設(shè)計(jì)
使用 sql 語(yǔ)句查詢?cè)瓟?shù)據(jù)庫(kù)數(shù)據(jù),再插入新數(shù)據(jù)庫(kù)。
查詢新數(shù)據(jù)庫(kù)表最后一條記錄。
根據(jù)條件是否創(chuàng)建新數(shù)據(jù)表,再查詢新數(shù)據(jù)庫(kù)最后一條記錄的 ID 值,以此為起點(diǎn)查詢舊數(shù)據(jù)庫(kù),因?yàn)檫w移只需要從已導(dǎo)入的最后一條開始即可,如果表不存在,則從 0 開始。
使用回調(diào)函數(shù),獲取舊數(shù)據(jù)庫(kù),處理數(shù)據(jù)(或舍棄,或修改,等),再插入到新數(shù)據(jù)庫(kù),直接用 xorm 的結(jié)構(gòu)體即可。xorm 可批量插入,但是舊數(shù)據(jù)庫(kù)無(wú)法做到批量獲取,查詢并存儲(chǔ)到切片中,到達(dá)一定數(shù)量(如3000條),再插入新數(shù)據(jù)庫(kù)。
選擇記錄
原數(shù)據(jù)庫(kù)為 sqlserver,表和列部分有中文,不符合 xorm 要求。只能使用 sql 語(yǔ)句操作。
新數(shù)據(jù)庫(kù)為 mysql,全英文,可用 xorm 結(jié)構(gòu)體映射,查詢、插入較方便。
試過將 xorm 的結(jié)構(gòu)體成員變量改為中文,在 Golang 中使用中文作為變量名是可以的,只是反射不成功。
實(shí)踐過程及知識(shí)點(diǎn)
數(shù)據(jù)庫(kù)連接
// mysql
root:root@tcp(8.8.8.8:3305)/mydb?charset=utf8&interpolateParams=true&parseTime=true&loc=Local
// mssql
server=8.8.8.8;user id=latelee;password=123456;database=mydb;encrypt=disable;
注意,使用github.com/denisenkom/go-mssqldb時(shí),需要添加encrypt=disable;,否則加不上。
xorm小知識(shí)
設(shè)置時(shí)區(qū):
engine.DatabaseTZ = time.UTC // time.Local
engine.TZLocation = time.UTC
是否顯示 sql 語(yǔ)句:
//engine.ShowSQL(true)
engine.ShowSQL(false)
xorm結(jié)構(gòu)體映射
xorm 使用結(jié)構(gòu)體與數(shù)據(jù)庫(kù)字段映射,各種操作十分方便。
設(shè)置完全映射:
engine.SetMapper(core.SameMapper{})
定義示例:
type TheData struct {
Id int `xorm:"int pk not null autoincr 'id'"` // autoincr
Money sql.NullFloat64 `xorm:"float default null"`
}
第一成員帶 id,在數(shù)據(jù)庫(kù)即為此名,第二成員不帶,將映射為 Money。
結(jié)構(gòu)體成員需大寫,否則反射失敗。
新數(shù)據(jù)庫(kù)創(chuàng)建
一般創(chuàng)建表使用其它的方法,但為了方便使用,直接在代碼中創(chuàng)建。
var sqlstr string
//sqlstr = "show tables like 'xxx'"
sqlstr = fmt.Sprintf("show tables like '%s'", newTableName)
_, err = engine.SQL(sqlstr).Count()
if err != nil { // 沒有找到現(xiàn)成函數(shù)判斷,用返回值
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
}
}
代碼先判斷是否存在數(shù)據(jù)表,不存在再調(diào)用engine.Sync2創(chuàng)建,該函數(shù)可同時(shí)創(chuàng)建多個(gè)數(shù)據(jù)表。不過似乎沒有直接的 API 接口判斷是否存在數(shù)據(jù)表,只好用 sql 語(yǔ)句查詢,判斷其返回值。
數(shù)據(jù)庫(kù)為空值
如果讀取到數(shù)據(jù)庫(kù)中的空值,會(huì)返回錯(cuò)誤:
Scan failed: sql: Scan error on column index 26, name "測(cè)試空值": converting driver.Value type string ("NULL") to a float32: invalid syntax
此問題可用 sql 包提供的類型解決。如sql.NullString、sql.NullFloat64等。這些類型實(shí)際是結(jié)構(gòu)體,包括了Valid成員,通過該值可判斷。
代碼重用
由于遷移過程是完全一樣的,只是數(shù)據(jù)庫(kù)、表名、字段等不同。可以將共用部分抽象成函數(shù),具體操作使用回調(diào)函數(shù)解決。
至于不同之處,則由調(diào)用者將其傳遞到共用函數(shù)中即可。
回調(diào)函數(shù)定義和使用
type DataCb func (rows *sql.Rows, engine *xorm.Engine, totalCnt int64) int
func MigrateDB(olddb, oldtable, newdb string, vnewtable interface{}, datacb DataCb) {
...
datacb(rows, engin, cnt)
}
在回調(diào)函數(shù)中,根據(jù)舊數(shù)據(jù)庫(kù)字段掃描,示例如:
for rows.Next() {
err := rows.Scan(&a, &b, &c, &d)
}
注意,Scan 參數(shù)個(gè)數(shù)和數(shù)據(jù)庫(kù)的列數(shù)相同,否則出錯(cuò)。可直接用新數(shù)據(jù)庫(kù)結(jié)構(gòu)體成員,如果不需要,可以使用其它變量。
結(jié)構(gòu)體映射
結(jié)構(gòu)體用指針傳遞,這樣可進(jìn)行通用處理,即 MigrateDB 不再與具體結(jié)構(gòu)體關(guān)聯(lián)。
// 獲取新表結(jié)構(gòu)體名稱
atype := reflect.TypeOf(vnewtable)
if atype.Kind() == reflect.Ptr { // 如果是結(jié)構(gòu)體指針,再獲取結(jié)構(gòu)體
atype = atype.Elem()
}
// 如果還不是結(jié)構(gòu)體,出錯(cuò)
if atype.Kind() != reflect.Struct {
log.Println("Check type error not Struct")
return
}
newTableName := atype.Name()
// 取第N個(gè)字段及值
idx := 0
var newIDName string = atype.Field(idx).Name// 此處為ID值,如果獲取不到tag,選擇變量名
// 查找單引號(hào),得到tag
xormStr := 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)
功能有二:獲取結(jié)構(gòu)體名稱,因?yàn)?sql 語(yǔ)句需要使用該名稱。獲取第 0 個(gè)字段名稱,結(jié)合實(shí)際情況,數(shù)據(jù)庫(kù)第 0 個(gè)字段為 ID,故設(shè)計(jì)上,將結(jié)構(gòu)體第 0 個(gè)字段置為 ID。
注意,這里使用結(jié)構(gòu)體指針形式傳遞,不需要額外聲明結(jié)構(gòu)體變量,代碼較整潔。
總結(jié)
以上是生活随笔為你收集整理的go 获得 mysql 实际运行 SQL,Golang实践录:一个数据库迁移的代码记录的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql hadoop架构,Debez
- 下一篇: linux cmake编译源码,linu