[GO]go语言的mysql操作


1.连接驱动

go语言自带的database/sql包提供了一个保证SQL或类SQL数据库的泛用接口,go它并不提供具体的数据库驱动。使用database/sql包时必须注入(至少)一个数据库驱动。我们一般使用一个第三方的包实现数据库的连接,比如mysql常用的是https://github.com/go-sql-driver/mysql这个驱动。

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

可以通过go get 指令下载对应的驱动。

使用open函数实现一个最简单的数据库驱动初始化,这个函数只会验证dsn的输入格式是否正确,不会实际创建数据库的连接。

open函数会返回一个db对象,下面使用了同名的db变量来接收,这个对象可以被多个go协程安全并发调用,因此,一般最后才会关闭db的连接。

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

func main() {
   // DSN:Data Source Name 
	dsn := "root:123456@tcp(127.0.0.1:3306)/sql_test"
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		panic(err)
	}
	defer db.Close()  // 注意这行代码要写在上面err判断的下面
}

2.数据库的连接

我们使用ping命令来测试open传入的dsn数据是否正确,数据库有没有被正确的连接上。

// 定义一个全局对象db,他是一个数据库连接单例,用于接收open返回的db对象
var db *sql.DB

// 定义一个初始化数据库的函数
func initDB() (err error) {
	// DSN:Data Source Name
	dsn := "root:123456@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
	// 给全局对象db传入open返回的db对象
	db, err = sql.Open("mysql", dsn)
	if err != nil {
		return err
	}
	// 尝试与数据库建立连接(校验dsn是否正确)
	err = db.Ping()
	if err != nil {
		return err
	}
	return nil
}

func main() {
	err := initDB() // 调用上面的初始化数据库的函数
	if err != nil {
		fmt.Printf("init db failed,err:%v\n", err)
		return
	}
}

db是表示连接的数据库对象(结构体实例),它保存了连接数据库相关的所有信息。它内部维护着一个具有零到多个底层连接的连接池,它可以安全地被多个goroutine同时使用。

3.CRUD操作

准备工作,先建个表

CREATE DATABASE sql_test;
use sql_test;
CREATE TABLE `user` (
    `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(20) DEFAULT '',
    `age` INT(11) DEFAULT '0',
    PRIMARY KEY(`id`)
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

3.1 查询

先在建的表上创建几个数据项。

定义一个结构体,用于存放返回的数据。

type user struct {
	id   int
	age  int
	name string
}

进行单行的查询,使用QueryRow函数,传入查询的sql语句,以及查询条件。

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

实际查询id=2的代码如下

var db *sql.DB
type user struct {
	id   int
	age  int
	name string
}

// 定义一个初始化数据库的函数
func initDB() (err error) {
	// DSN:Data Source Name
	dsn := "root:123456@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
	db, err = sql.Open("mysql", dsn)
	if err != nil {
		return err
	}
	// 尝试与数据库建立连接(校验dsn是否正确)
	err = db.Ping()
	if err != nil {
		return err
	}
	return nil
}
// 查询单条数据示例
func queryRowDemo() {
	sqlStr := "select id, name, age from user where id=?"
	var u user
	// 非常重要:确保QueryRow之后调用Scan方法,否则持有的数据库链接不会被释放
	//func (db *DB) QueryRow(query string, args ...interface{}) *Row
	err := db.QueryRow(sqlStr, 2).Scan(&u.id, &u.name, &u.age)
	if err != nil {
		fmt.Printf("scan failed, err:%v\n", err)
		return
	}
	fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
}
func main() {
	err := initDB() // 调用输出化数据库的函数
	if err != nil {
		fmt.Printf("init db failed,err:%v\n", err)
		return
	}
	queryRowDemo()
}

返回结果

进行多行的查询也类似,使用Query函数,传入查询的sql语句,以及查询条件。

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

查询id>0的多行查询示例如下:

// 查询多条数据示例
func queryMultiRowDemo() {
	sqlStr := "select id, name, age from user where id > ?"
	rows, err := db.Query(sqlStr, 0)
	if err != nil {
		fmt.Printf("query failed, err:%v\n", err)
		return
	}
	// 非常重要:关闭rows释放持有的数据库链接
	defer rows.Close()

	// 循环读取结果集中的数据
	for rows.Next() {
		var u user
		err := rows.Scan(&u.id, &u.name, &u.age)
		if err != nil {
			fmt.Printf("scan failed, err:%v\n", err)
			return
		}
		fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
	}
}

3.2 插入

插入、删除、更新都是通过Exec函数实现的:

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

首个参数为sql操作指令,后面的可变参数传入sql指令的参数,下面是一个插入示例。插入一个name=王五,age=38的数据项。

// 插入数据
func insertRowDemo() {
	sqlStr := "insert into user(name, age) values (?,?)"
	ret, err := db.Exec(sqlStr, "王五", 38)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	theID, err := ret.LastInsertId() // 新插入数据的id
	if err != nil {
		fmt.Printf("get lastinsert ID failed, err:%v\n", err)
		return
	}
	fmt.Printf("insert success, the id is %d.\n", theID)
}

3.3 删除

删除同样使用的Exec函数,下面给出一个删除示例,删除id=3的数据项。

// 删除数据
func deleteRowDemo() {
	sqlStr := "delete from user where id = ?"
	ret, err := db.Exec(sqlStr, 3)
	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)
}

3.4 更新

更新同样使用Exec函数,下面给出一个更新示例,将id=3的数据项的age设置为39。

// 更新数据
func updateRowDemo() {
	sqlStr := "update user set age=? where id = ?"
	ret, err := db.Exec(sqlStr, 39, 3)
	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)
}

4.go语言实现预处理

所谓预处理,就是将sql语句和sql执行的参数分成两次传递到sql服务器上,sql可以先解析sql语句,再在服务器上根据后续传入的参数进行相应占位符(mysql中占位符为?)位置的替换,合成实际的sql指令,将结果返回用户,而不必等待用户将sql语句和参数一起传入服务器统一解析。

预处理的优点:优化MySQL服务器重复执行SQL的情况,提升服务器性能,提前让服务器编译,一次编译多次执行,节省后续编译的成本。也可以避免SQL注入问题。

go使用Prepare函数实现预处理,他会将传入的sql语句先传到sql服务器,等待参数的传入。返回一个连接对象,用于后续的命令。

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

对查询进行预处理示例如下:

// 预处理查询示例
func prepareQueryDemo() {
	sqlStr := "select id, name, age from user where id > ?"
    //使用stmt接收返回的连接状态,需要关闭
	stmt, err := db.Prepare(sqlStr)
	if err != nil {
		fmt.Printf("prepare failed, err:%v\n", err)
		return
	}
	defer stmt.Close()
    //rows用于接收传入参数后sql服务返回的结果
	rows, err := stmt.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, &u.age)
		if err != nil {
			fmt.Printf("scan failed, err:%v\n", err)
			return
		}
		fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
	}
}

5.go实现事务管理

事务定义:是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元);

在MySQL中只有使用了Innodb数据库引擎的数据库或表才支持事务。事务处理可以用来维护数据库的完整性,保证成批的SQL语句要么全部执行,要么全部不执行。

事务具有四个特性ACID,原子性,一致性,持久性,隔离性。

go通常使用以下三个函数实现对事务的管理,分别对应着开始,提交和回滚事务:

func (db *DB) Begin() (*Tx, error)
func (tx *Tx) Commit() error
func (tx *Tx) Rollback() error

使用事务管理的示例如下,他可以保证ret1和ret2都被执行或都不执行:

// 事务操作示例
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 age=30 where id=?"
	ret1, err := tx.Exec(sqlStr1, 2)
	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 age=40 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!")
}

相关