[GO]go语言的mysql操做

2021年11月20日 阅读数:1
这篇文章主要向大家介绍[GO]go语言的mysql操做,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

1.链接驱动

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

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

能够经过go get 指令下载对应的驱动。git

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

open函数会返回一个db对象,下面使用了同名的db变量来接收,这个对象能够被多个go协程安全并发调用,所以,通常最后才会关闭db的链接。sql

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 查询

先在建的表上建立几个数据项。并发

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

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!")
}