安装驱动

go中database/sql已封装好了完整的sql使用API,但没有驱动,需要导入对应数据库的驱动。

在这里以mysql为例,Go MySQL Driver是 Godatabase/sql/driver接口的一个实现,只需导入驱动程序即可。

在项目demo终端下载驱动:

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

连接数据库

原生包中func Open(driverName, dataSourceName string ) (* DB , error )函数打开数据库,返回一个DB对象,DB 是一个数据库句柄,表示一个包含零个或多个底层连接的池。多个 goroutine 并发使用它是安全的。。

其中dervierName指定数据库名称,dataSourceName为数据源名称(DSN)。

DSN其格式为username:password@protocol(address)/dbname?param=value,其中dbname为要连接的库名字

 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
import (
	"database/sql"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
)
//创建全局db对象
var db *sql.DB

func initDB() (err error) {
	dsn := "root:123456@tcp(127.0.0.1:3306)/sys?charset=utf8mb4&parseTime=True"
	db, err = sql.Open("mysql", dsn)
	if err != nil {
		panic(err)
	}
  
  //ping为测试是否连通,确保真正连接上
	err = db.Ping()
	if err != nil {
		fmt.Printf("connect failed,err:%v", err)
		return
	}
	return
}

func main() {
	err := initDB()
	if err != nil {
		fmt.Printf("init db failed,err:%v\n", err)
		return
	}
	defer db.Close()
}

基本使用

通过创建user表,同以下结构,进行CRUD

创建user结构体

1
2
3
4
5
type user struct {
	id   int
	name string
	age  int
}

Query

查询一行

func (db * DB ) QueryRow(query string , args ... any ) * Row

QueryRow 执行最多返回一行的查询。QueryRow 总是返回一个非零值。错误会延迟到调用 Row Scan 方法时出现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func queryRow() {
	var u1 user
	queryStr := "select * from user where id = ?"
	row := db.QueryRow(queryStr, 1)
	err := row.Scan(&u1.id, &u1.name, &u1.age)
	if err != nil {
		fmt.Printf("queryRow error: %v", err)
		return
	}
	fmt.Println(u1)
}

查询多行

func (db * DB ) Query(query string , args ... any ) (* Rows , error )

Query 执行返回多行的查询

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func query() {
	userSlice := make([]user, 0, 2)
	rows, _ := db.Query("select * from user")
  defer rows.Close() //在这里调用close是因为不确定scan时是否会出错,如出错,而rows又会一直占用连接,此时应该关闭
	for rows.Next() {
		var u user
		err := rows.Scan(&u.id, &u.name, &u.age)
		if err != nil {
			fmt.Printf("query error: %v", err)
			continue
		}
		userSlice = append(userSlice, u)
	}
	fmt.Println(userSlice)
}

Exec

Exec 执行查询而不返回任何行,插入、更新和删除操作都使用它。

Exec返回一个Result实例

1
2
3
4
5
6
type Result interface {
 // LastInsertId 返回数据库生成的整数,即为自增的id。并非所有数据库都支持此功能,并且此类语句的语法各不相同
	LastInsertId() ( int64 , error )
  // RowsAffected 返回受 更新、插入或删除影响的行数。并非每个数据库或数据库驱动程序都支持这一点。
	RowsAffected() ( int64 ,错误) 
}	

插入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func execInsert() {
	insertSql := "insert into user(name,age) values(?,?)"
	insertRes, err := db.Exec(insertSql, "汪汪汪汪", 24)
	if err != nil {
		fmt.Printf("insert failed, err:%v", err)
		return
	}
	insertId, _ := insertRes.LastInsertId() //最后插入的id
	rowsAffected, _ := insertRes.RowsAffected() //影响的行数
	fmt.Println(insertId, rowsAffected)
}

更新

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func execUpdate() {
	updateSql := "update user set age = ? where id = ?"
	res, err := db.Exec(updateSql, 18, 3)
	if err != nil {
		fmt.Printf("update failed, err:%v", err)
		return
	}
	rowsAffected, _ := res.RowsAffected()
	fmt.Println(rowsAffected)
}

删除

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func execDelete() {
	delectSql := "delete from user where id = ?"
	res, err := db.Exec(delectSql, 1)
	if err != nil {
		fmt.Printf("delete failed, err:%v", err)
		return
	}
	rowsAffected, _ := res.RowsAffected()
	fmt.Println(rowsAffected)
}

预处理

在以上这些CRUD中,使用的都是即时SQL,数据库接收到一条sql后,其执行流程大致如下:

​ 1.词法和语义解析

​ 2.优化 SQL 语句,制定执行计划

​ 3.执行并返回结果

一次编译,一次运行,而大部分情况下,一条sql其实是一直被反复运行的,如单纯的查询操作,又或者是sql中的参数不同,其他都一样,如果每次都要经历这样的流程去解析,无疑会降低效率。

预处理通过将一般sql语句模版化,做到一次编译,多次运行,提高效率。

使用下面函数来进行预处理

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func prepare() {
	stmt, err := db.Prepare("select * from user where id = ?")
	if err != nil {
		fmt.Printf("exec Prepare failed, err:%v", err)
		return
	}
	var u user
	err = stmt.QueryRow(4).Scan(&u.id, &u.name, &u.age)
	if err != nil {
		fmt.Printf("queryRow err:%v", err)
		return
	}
	fmt.Println(u)
}

其他的操作也大同小异。

事务

sql包中,有3个关于事务的主要方法。

func (db *DB) Begin() (*Tx, error):开启事务

func (tx *Tx) Commit() error:提交事务

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
func tx() {
	tx, err := db.Begin()
	if err != nil {
		fmt.Printf("tx begin error : %v", err)
		return
	}
	updateSql := "update user set age = ? where id = ?"
	_, err = tx.Exec(updateSql, 38, 2)
	if err != nil {
		fmt.Printf("error:%v", err)
		tx.Rollback()
		return
	}
	_, err = tx.Exec(updateSql, 29, 4)
	if err != nil {
		fmt.Printf("error:%v", err)
		tx.Rollback()
		return
	}
	tx.Commit()
	fmt.Println("ok")
}

加入预处理的事务

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
func txPrepare() {
	tx, err := db.Begin()
	if err != nil {
		fmt.Printf("tx begin error : %v", err)
		return
	}
	stmt, _ := tx.Prepare("update user set age = ? where id = ?")
	_, err = stmt.Exec(28, 2)
	if err != nil {
		fmt.Printf("error:%v", err)
		tx.Rollback()
		return
	}
	_, err = stmt.Exec(22, 4)
	if err != nil {
		fmt.Printf("error:%v", err)
		tx.Rollback()
		return
	}
	tx.Commit()
	fmt.Println("ok")
}