最近在看gin框架的使用,众多的文档和教程中都比较推荐使用 gorm 来操作数据库,但是我本人对orm实在无感,所在还是学习一下使用原生的sql来操作MySQL吧。

在github上 https://github.com/go-sql-driver/mysql 的start 数最多,维护的也比较好,决定先拿它练练基础的curd吧。

一、准备数据

使用 docker 启一个mysql容器,简单构建张表,随便插入几条数据

1
2
3
4
5
6
7
8
9
+----+------+-----+
| id | name | age |
+----+------+-----+
|  1 | yyx  |  10 |
|  2 | 杨   |  20 |
|  3 | 彦   |  30 |
|  4 | 星   |  35 |
|  5 | f    |   0 |
+----+------+-----+

二、连接数据库

本文并没有在gin框架下使用,而是简单的使用demo 程序,具体在项目中还需要再对其进行封装。

go 语言标准库中的有个database/sql 包,这里定义了很多接口与方法,而github.com/go-sql-driver/mysql 库很好的实现了这些接口。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package db

import (
	"database/sql"
	"fmt"
	"time"

	_ "github.com/go-sql-driver/mysql"
)

func InitDB() *sql.DB {
	dsn := fmt.Sprintf("%s:%s@(%s)/%s?charset=%s&parseTime=true&loc=Local",
		"root", "123456", "127.0.0.1:3306", "gintest", "utf8")

	if conn, err := sql.Open("mysql", dsn); err != nil {
		panic(err.Error())
	} else {
		conn.SetConnMaxLifetime(7 * time.Second) //设置空闲时间,这个是比mysql 主动断开的时候短
		conn.SetMaxOpenConns(10)
		conn.SetMaxIdleConns(10)
		return conn
	}
}

sql.Open 方法开始连接数据库,第一个参数为所要连接的数据库类型。第二个参数为dsn,可以理解为连接数据库的参数信息。

这里在连接以后,又对连接进行了三个设置。

conn.SetConnMaxLifetime 为设置连接的空闲时间,为什么要设置这个参数以及该设置多少是有讲究的。

有的MySQL服务器为了性能考虑,会设置主动断开空闲连接的,默认8个小时,但是一般的dba不会设置那么长,很有可能会设置10秒或者更短,所以这个参数要设置的更短,这个参数可以登录MySQL 服务器执行show global variables like '%timeout%'; 来查看,有个wait_timeout 值,这里的值要设置比这个值更短。

conn.SetMaxOpenConns(10)conn.SetMaxOpenConns(10) 这两个方法会初始化一个连接池,保证池子里会有足够的连接,这个值要设置多少需要根据应用的并发情况。

但是SetMaxIdleConns 的值不要小于SetMaxOpenConns 设置的值,否则会导致频繁的创建连接。

官方的解释为

db.SetMaxIdleConns() is recommended to be set same to db.SetMaxOpenConns(). When it is smaller than SetMaxOpenConns(), connections can be opened and closed much more frequently than you expect

三、查询数据

上面数据库连接成功以后,会返回一个 *sql.DB 对象,使用这个对象就可以进行curd操作了。

使用Query查询所有符合条件的记录

Query 方法返回*sql.Rows, 之后可以通过遍历Rows来得到数据

这里主要的代码为

 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
package main

import (
    "fmt"
    "sqltest/db"
)

type person struct {
    id   int
    name string
    age  int
}

func main() {
    conn := db.InitDB()
    defer conn.Close()
    // Query 返回的是Rows
    rows, err := conn.Query("select * from yyx_test")
    if err != nil {
        fmt.Println(err.Error())
    }
    // 将result 解析成 结构体对象
    var persons = make([]person, 0)
    for rows.Next() {
        var a person
        rows.Scan(&a.id, &a.name, &a.age)
        persons = append(persons, a)
    }
    fmt.Println(persons)
}

这里有两行代码比较关键

1
2
var a person
rows.Scan(&a.id, &a.name, &a.age)

Scan copies the columns in the current row into the values pointed at by dest. The number of values in dest must be the same as the number of columns in Rows.

定义一个person 结构体对象,然后将使rows.Scan 方法将每一行的记录复制到对象中,所以这里要传入指针,否则原对象的值是不会改变的。

这里注意,使用Query 函数查询时用的是select * from yyx_test 所以返回的字段是和数据库表中的是一样的,我创建表时用的是id=>int, name=>string, age=>int , 所以这里也需要使用同类型的变量指针接收。

很多文章教程中都会说这里必须要定义一个结构体变量,其实可以不用定义,只要保证Scan函数里的指针变量类型与数据库字段类型对应就可以,完全可以使用以下来接收。

1
2
3
var id, age int
var name string
rows.Scan(&id, &name, &age)

使用QueryRow来返回一条数据

QueryRow的使用和Query几乎一样,只是它返回的不是Rows,而是Row,一条记录,如果sql语句查询到多条记录,那也只返回第一条。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func main() {
	conn := db.InitDB()
	defer conn.Close()
	row := conn.QueryRow("select * from user_infos where id = ?", 10)
	var u user
	err := row.Scan(&u.id, &u.name, &u.age)
	if err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(u)
}

如果没有查询到任何记录的话,则Scan会返回err,如上面的sql语句,数据库并没有id为10 的记录,则会打印出sql: no rows in result set

这点比Query 好,Query返回*Rows,error, 如果查询不到数据的话是没有error, 只有当sql 语句有问题或者数据库本身的问题如连接中断了才会记录在error中。

使用Stmt语句进行查询

有时候一条语句可能会在多条查询中使用,比如

1
2
3
select * from yyx_test where id = 10
select * from yyx_test where id = 20
select * from yyx_test where id = 30

这时就可用到Stmt, 将公共的部分抽出来

1
stmt, err := conn.Prepare("select * from yyx_test where id=?")

之后就可以使用stmt的Query或者QueryRow方法了

1
rows, err := stmt.Query(1) // 相当于select * form yyx_test where id = 1

Prepare 方法中有几个? 在Query方法中也需要传入几个参数

参考链接

https://pkg.go.dev/database/sql

https://github.com/go-sql-driver/mysql