略微加速

略速 - 互联网笔记

golang从MySQL中读取全部字段

2019-03-13 leiting (3026阅读)

标签 Golang

用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{}了,直接返回错误。


http://noops.me/?p=1128


北京半月雨文化科技有限公司.版权所有 京ICP备12026184号-3