用golang开发服务端程序,开发效率和程序运行效率都还蛮不错。但是有些功能实现起来,也还是挺蛋疼的。
最近,由于数据库中间层项目需要监测从库的同步状态,屏蔽同步故障和同步延迟较大的从库,因此需要监测从库的slave status。这个功能用其它语言实现起来非常简单,但是golang却把笔者卡住了半天时间。试来试去,最终总算解决(汗)。
大体过程如下:
查看从库状态,大家都知道用
1 | SHOW SLAVE STATUS |
,golang代码写起来也不难:
1 | rows, err := db.Query("SHOW SLAVE STATUS") |
获取结果的时候问题来了,不同版本的MySQL返回的结果列数不一样,笔者用的MySQL是官方5.5版本,会返回40列数据,肯定不能用枚举的方式。OK,先试试字符串Slice。
1 2 3 4 5 | cols, _ := rows.Columns() data := make([]string, len(cols)) for rows.Next() { rows.Scan(data...) } |
程序运行报错:
1 | cannot use data (type []string) as type []interface {} in function argument |
貌似只能接受interface{}类型的参数,那咱换下变量类型:
1 2 3 4 5 | cols, _ := rows.Columns() data := make([]interface{}, len(cols)) for rows.Next() { rows.Scan(data...) } |
运行还是报错(晕…):
1 | sql: Scan error on column index 0: destination not a pointer |
貌似无解了~~(抓狂中,此处省略N字)
一顿google之后,找到一种解法:
1 2 3 4 5 6 7 8 9 | buff := make([]interface{}, len(cols)) // 临时slice,用来通过类型检查 data := make([]string, len(cols)) // 真正存放数据的slice for i, _ := range buff { buff[i] = &data[i] // 把两个slice关联起来 }
for rows.Next() { rows.Scan(buff...) } |
运行一下试试,泪流满面啊……
期待中的结果终于出来了。把列名加上一起输出:
1 2 3 | for k, col := range data { fmt.Printf("%10s:\t%10s\n", cols[k], col) } |
这种方式也能用于获取返回列数不定的场景。
最后: 或许是笔者的功力太浅,目前只能找到这种解决办法,如果那位大侠有更优雅的解决方案,希望告知我一下。
下面是完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | func slaveInfo() { db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8&timeout=%s", USER, PASS, HOST, PORT, TOUT)) if err != nil { log.Fatalf("Mysql DSN Error: %v\n", err) }
if err = db.Ping(); err != nil { log.Fatalf("Fail to connect to mysql: %v\n", err) } defer db.Close()
rows, err := db.Query("SHOW SLAVE STATUS") if err != nil { log.Fatalf("Query fail: %v\n", err) }
cols, _ := rows.Columns() buff := make([]interface{}, len(cols)) // 临时slice data := make([]string, len(cols)) // 存数据slice for i, _ := range buff { buff[i] = &data[i] }
for rows.Next() { rows.Scan(buff...) // ...是必须的 }
for k, col := range data { fmt.Printf("%30s:\t%s\n", cols[k], col) } } |
研究了一下Scan的源码,主要涉及到的代码如下:
1 2 3 4 5 6 7 8 9 10 11 | // 行参可以接受任意数量、任意类型的变量 func (rs *Rows) Scan(dest ...interface{}) error { for i, sv := range rs.lastcols { // convertAssign函数主要是将数据sv拷贝到dest[i]中 err := convertAssign(dest[i], sv) if err != nil { return fmt.Errorf("sql: Scan error on column index %d: %v", i, err) } } return nil } |
convertAssign函数主要过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | func convertAssign(dest, src interface{}) error { // 常用类型, 直接就赋值了 switch s := src.(type) { case string: case []byte: case nil: }
// 其它类型就用反射来处理,注意都要用指针类型 var sv reflect.Value
switch d := dest.(type) { case *string: case *[]byte: case *RawBytes: case *bool: case *interface{}: }
// 如果为非指针类型,则抛出上面看到的错误 dpv := reflect.ValueOf(dest) if dpv.Kind() != reflect.Ptr { return errors.New("destination not a pointer") } |
Scan函数中如果只是传入[]interface{}类型的变量,在convertAssign函数里,dest的类型就是interface{}了,直接返回错误。
最新评论: