连接

go语言中的database/sql包提供了Sql数据库的泛用接口,但是不提供具体的驱动。使用这个包时必须注入数据库驱动。

下载依赖

1
go get -u github.com/go-sql-driver/mysql

使用MySql驱动

1
func Open(driverName, dataSourceName string) (*DB, error)

打开一个指定driverName的数据库,dataSourceName指定数据源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

import (
"database/sql"
"fmt"

_ "github.com/go-sql-driver/mysql"
)
func Open() {
// DSN:data source name
dsn := "root:123@tcp(127.0.0.1:3306)/t1231est"
db, err := sql.Open("mysql", dsn)

if err != nil {
fmt.Printf("打开失败,db:%#v", db)
panic(err)
}
defer db.Close() // 写在error判断的下面
fmt.Printf("成功打开数据库,db:%#v", db)
}

初始化连接

Open函数只有检查参数格式的作用,并不创建与数据库的连接,如需检查数据源是否有效应该使用Ping方法。

Open得到的DB对象可以被多个goroutine并发使用,并且维护一个空闲的连接池。所以Open函数应该只被调用一次,很少关闭DB对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 定义一个全局变量
var db *sql.DB // 表示连接的数据库对象, 内部维护着一个连接池,可以安全的被多个goroutine使用

func initDB() (err error) {
dsn := "root:123@tcp(127.0.0.1:3306)/tes22t"
db, err = sql.Open("mysql", dsn)
if err != nil {
fmt.Printf("打开失败,db:%#v", db)
return err
}
// 尝试与数据库建立连接
err = db.Ping()
if err != nil {
return err
}
return nil
}

SetMaxOpenConns

1
func (db *DB) SetMaxOpenConns(n int)

设置与数据库建立连接的最大数目。n≤0时连接数无限制,0<n<最大闲置连接数时,最大闲置连接数会减小到最大连接数。

SetMaxIdleConns

1
func (db *DB) SetMaxIdleConns(n int)

设置连接池中的最大闲置连接数。n ≤ 0时不会保留闲置连接,n > 最大开启连接数时,最大闲置连接数减小到最大开启连接数。

CRUD

创建表

选定或新建一个数据库,使用以下SQL创建一张表

1
2
3
4
5
CREATE TABLE `user` (
`id` int(4) NOT NULL,
`name` varchar(32) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

查询

首先封装结构体代表表中的每一行记录

1
2
3
4
5
// 创建结构体封装数据库查询对象
type user struct {
id int
name string
}

单行查询

通过db.QueryRow()来查询单行数据,期望返回最多一行结果。

返回值的Scan方法被调用时才会返回被延时的错误。

1
func (db *DB) QueryRow(query string, args ...interface{}) *Row

示例:

1
2
3
4
5
6
7
8
9
10
11
func queryRowDemo() {
sqlStr := "select id, name from user where id = ?"
var u user
// 确保QueryRow之后调用Scan方法,否则持有的数据库连接不会被释放
err := db.QueryRow(sqlStr, 1).Scan(&u.id, &u.name)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return
}
fmt.Printf("id:%d name:%s\n", u.id, u.name)
}

多行查询

db.Query()执行一次查询返回多行数据

1
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func queryMultiRowDemo() {
sqlStr := "select id, name from user where id > 0"
rows, err := db.Query(sqlStr)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return
}
// 非常重要,关闭rows释放所有连接数据
defer rows.Close()
// 循环读取结果集中的数据
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.name)
if err != nil {
fmt.Printf("Scan failed ,err: %v\n", err)
return
}
fmt.Printf("id:%d name:%s\n", u.id, u.name)
}
}

插入

插入、更新和删除操作都使用Exec方法。

1
func (db *DB) Exec(query string, args ...interface{}) (Result, error)

Result是对已执行的SQL命令的总结, 参数args表示query中的占位参数。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func insertRowDemo() {
sqlstr := "insert into user (id, name) values (?, ?)"
ret, err := db.Exec(sqlstr, 223, "王五")
if err != nil {
fmt.Printf("insert failed, err:%v\n", err)
return
}
theID, err := ret.LastInsertId()
if err != nil {
fmt.Printf("get lastinsert ID failed, err:%v\n", err)
return
}
fmt.Printf("insert success, the id is %d.\n", theID)
}

更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func updateRowDemo() {
sqlStr := "update user set name = ? where id = ?"
ret, err := db.Exec(sqlStr, "张s", 222)
if err != nil {
fmt.Printf("update failed, err:%v\n", err)
return
}
n, err := ret.RowsAffected()
if err != nil {
fmt.Printf("get RowsAffected failed, err:%v\n", err)
return
}
fmt.Printf("update success, affected rows:%d\n", n)
}

删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func deleteRowDemo() {
sqlstr := "delete from user where id = ?"
ret, err := db.Exec(sqlstr, 2)
if err != nil {
fmt.Printf("delete failed, err:%v\n", err)
return
}
n, err := ret.RowsAffected()
if err != nil {
fmt.Printf("get RowsAffected failed, err:%v\n", err)
return
}
fmt.Printf("delete success, affected rows:%d\n", n)
}

预处理

概念

不使用预处理时完整的SQL语句会发送给客户端,会有SQL注入的风险。使用预处理可以将SQL与数据拆分,先将要执行的SQL“模板”发送给数据库预处理,之后再将占位符参数发送给数据库,这样就可以防止SQL注入。

普通SQL语句执行过程:

  1. 客户端对SQL语句进行占位符替换得到完整的SQL语句。
  2. 客户端发送完整SQL语句到MySQL服务端
  3. MySQL服务端执行完整的SQL语句并将结果返回给客户端。

预处理执行过程:

  1. 把SQL语句分成两部分,命令部分与数据部分。
  2. 先把命令部分发送给MySQL服务端,MySQL服务端进行SQL预处理。
  3. 然后把数据部分发送给MySQL服务端,MySQL服务端对SQL语句进行占位符替换。
  4. MySQL服务端执行完整的SQL语句并将结果返回给客户端。

优点

  1. 优化MySQL服务器重复执行SQL的方法,可以提升服务器性能,提前让服务器编译,一次编译多次执行,节省后续编译的成本。
  2. 避免SQL注入问题。

Go实现MySql预处理语句

database/sql中使用下面的Prepare方法来实现预处理操作。

1
func (db *DB) Prepare(query string) (*Stmt, error)

返回值可以同时执行多个查询和命令。

查询示例:

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 prepareQueryDemo() {
sqlstr := "select id, name from user where id > ?"
s, err := db.Prepare(sqlstr)
if err != nil {
fmt.Printf("prepare failed, err:%v", err)
return
}
defer s.Close()
rows, err := s.Query(0)
if err != nil {
fmt.Printf("query failed, err:%v\n", err)
return
}
defer rows.Close()
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.name)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return
}
fmt.Printf("id:%d name:%s\n", u.id, u.name)
}
}

插入、删除、更新示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func prepareInsertDemo() {
sqlStr := "insert into user (id, name) values (?, ?)"
s, err := db.Prepare(sqlStr)
if err != nil {
fmt.Printf("prepare failed err:%v", err)
return
}
defer s.Close()
_, err = s.Exec(123, "大王子")
if err != nil {
fmt.Printf("insert failed err:%v", err)
return
}
_, err = s.Exec(124, "小公主")
if err != nil {
fmt.Printf("insert failed err:%v", err)
return
}

fmt.Printf("insert succeed")
}

Go实现MySql事务

开启事务

1
func (db *DB) Begin() (*Tx, error)

提交事务

1
func (tx *Tx) Commit() error

回滚事务

1
func (tx *Tx) Rollback() error

示例

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
func transactionDemo() {
tx, err := db.Begin() // 开启一个事务
if err != nil {
if tx != nil {
tx.Rollback()
}
fmt.Printf("begin trans failed , err:%v\n", err)
return
}
sqlStr1 := "Update user set name = ? where id = ?"
ret1, err := tx.Exec(sqlStr1, "大公主", 124)
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("exec sql1 failed, err:%v\n", err)
return
}
affRow1, err := ret1.RowsAffected()
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n", err)
return
}

sqlStr2 := "Update user set name='王大锤' where id=?"
ret2, err := tx.Exec(sqlStr2, 3)
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("exec sql2 failed, err:%v\n", err)
return
}
affRow2, err := ret2.RowsAffected()
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n", err)
return
}
fmt.Println(affRow1, affRow2)
if affRow1 == 1 && affRow2 == 1 {
fmt.Println("事务提交啦...")
tx.Commit() // 提交事务
} else {
tx.Rollback()
fmt.Println("事务回滚啦...")
}

fmt.Println("exec trans success!")

}