Go语言操作Mysql

1,890 阅读18分钟

「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战」。

Go语言操作Mysql

Go语言操作Mysql

::: tip 说明 Go语言中的database/sql不包含数据库驱动,database/sql提供了保证SQL或类SQL数据库的泛用接口。使用sql包时必须注入(至少)一个数据库驱动。 参见golang.org/s/sqldriver… 获取驱动列表。 更多用法示例,参见wiki页面:golang.org/s/sqlwiki。 :::

1 连接MySQL

通过GO页面作为客户端访问数据库

  • 1.因为GO语言没有提供任何官方数据库驱动,所以需要安装第三方函数库。
  • 2.由于在github上安装,所以需要安装git软件,安装git教程

1.1 下载依赖

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

1.2 使用MySQL驱动

Open打开一个driverName指定的数据库驱动,dataSourceName指定数据源具有一定的格式连接信息字符串。

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

1.3 示例代码

package main

import (
	"database/sql"
	"fmt"
//执行driver.go文件中的init(),向"database/sql"注册一个mysql的驱动
	_ "github.com/go-sql-driver/mysql" 
)

func main() {
	dsn := "root:admin@tcp(127.0.0.1:3306)/go_test?charset=utf8"
	//Open打开一个driverName指定的数据库,dataSourceName指定数据源
	//不会校验用户名和密码是否正确,只会对dsn的格式进行检测
	db, err := sql.Open("mysql", dsn)
	if err != nil { //dsn格式不正确的时候会报错
		fmt.Printf("打开数据库失败,err:%v\n", err)
		return
	}
	//尝试连接数据库,Ping方法可检查数据源名称是否合法,账号密码是否正确。
	err = db.Ping()
	if err != nil {
		fmt.Printf("连接数据库失败,err:%v\n", err)
		return
	}
	fmt.Println("连接数据库成功!")
}

2 初始化连接

2.1 DB(数据库对象)

sql.DB类型代表了数据库,其它语言操作数据库的时候,需要创建一个连接,对于Go而言则是需要创建一个数据库类型, 它不是数据库连接,Go中的连接来自内部实现的连接池,连接的建立是惰性的,连接将会在操作的时候,由连接池创建并维护。

//使用 sql.Open函数创建数据库类型,第一个是数据库驱动名,第二个是连接信息的字符串
var Db *sqlx.DB
db, err := sqlx.Open("mysql","username:password@tcp(ip:port)/database?charset=utf8")
Db = db

2.2 DB连接池

Open函数可能只是验证其参数,而不创建与数据库的连接。如果要检查数据源的名称是否合法,应调用返回值的Ping方法。 返回的DB可以安全的被多个goroutine同时使用,并会维护自身的闲置连接池。这样一来,Open函数只需调用一次。很少需要关闭DB。

package main

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

var (
	// 定义一个全局对象db
	 db *sql.DB
	//定义数据库连接的相关参数值
	//连接数据库的用户名
	userName  string = "root"
	//连接数据库的密码
	password  string = "admin"
	//连接数据库的地址
	ipAddress string = "127.0.0.1"
	//连接数据库的端口号
	port      int    = 3306
	//连接数据库的具体数据库名称
	dbName    string = "go_test"
	//连接数据库的编码格式
	charset   string = "utf8"
)

func initDB() (err error) {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddress, port, dbName, charset)
	//Open打开一个driverName指定的数据库,dataSourceName指定数据源
	//不会校验用户名和密码是否正确,只会对dsn的格式进行检测
	db, err = sql.Open("mysql", dsn)
	if err != nil { //dsn格式不正确的时候会报错
		return err
	}
	//尝试与数据库连接,校验dsn是否正确
	err = db.Ping()
	if err != nil {
		fmt.Println("校验失败,err", err)
		return err
	}
	// 设置最大连接数
	db.SetMaxOpenConns(50)
	// 设置最大的空闲连接数
	// db.SetMaxIdleConns(20)
	fmt.Println("连接数据库成功!")
	return nil
}
func main() {
	err := initDB()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
}

3 数据库CRUD

3.1 建库建表语句

CREATE DATABASE go_test;
use go_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=utf8;

3.2 DB.Exec方法说明

插入、更新和删除操作都使用Exec()方法。Exec执行一次命令(包括查询、删除、更新、插入等),返回的Result是对已执行的SQL命令的总结。参数args表示query中的占位参数。

  • 插入数据后可以通过 LastInsertId() 方法获取插入数据的主键 id
  • 通过 RowsAffected 可以获取受影响的行数
  • 通过 Exec() 方法插入数据,返回的结果是 sql.Result 类型
func (db *DB) Exec(query string, args ...interface{}) (Result, error)

需要注意的是,不同的数据库,使用的占位符不同,mysql采用?作为占位符,其余数据库占位符说明如下:

数据库占位符语法
MySQL?
PostgreSQL1,1, 2等
SQLite? 和$1
Oracle:name

3.3 插入数据

package main

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

var (
	// 定义一个全局对象db
	 db *sql.DB
	//定义数据库连接的相关参数值
	//连接数据库的用户名
	userName  string = "root"
	//连接数据库的密码
	password  string = "admin"
	//连接数据库的地址
	ipAddress string = "127.0.0.1"
	//连接数据库的端口号
	port      int    = 3306
	//连接数据库的具体数据库名称
	dbName    string = "go_test"
	//连接数据库的编码格式
	charset   string = "utf8"
)

func initDB() (err error) {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddress, port, dbName, charset)
	//Open打开一个driverName指定的数据库,dataSourceName指定数据源
	//不会校验用户名和密码是否正确,只会对dsn的格式进行检测
	db, err = sql.Open("mysql", dsn)
	if err != nil { //dsn格式不正确的时候会报错
		return err
	}
	//尝试与数据库连接,校验dsn是否正确
	err = db.Ping()
	if err != nil {
		fmt.Println("校验失败,err", err)
		return err
	}
	// 设置最大连接数
	db.SetMaxOpenConns(50)
	// 设置最大的空闲连接数
	// db.SetMaxIdleConns(20)
	fmt.Println("连接数据库成功!")
	return nil
}

//创建一个和数据库表结构一样的机构体
type user struct {
	id   int
	name string
	age  int
}
func main() {
	err := initDB()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//新建一个user的结构体变量
	newUser := user{
		name: "赵六",
		age:  98}
	insertRow(newUser)
}

//向数据表中插入数据
// 参数说明newUser   ----user结构体
func insertRow(newUser user) {
	//需要插入的sql语句,?表示占位参数
	sqlStr := "insert into user(name,age) values(?,?)"
	//把user结构体的name、age字段依次传给sqlStr的占位参数
	ret, err := db.Exec(sqlStr, newUser.name, newUser.age)
	if err != nil { //执行sql语句报错
		fmt.Println("插入失败,err", err)
		return
	}
	newID, err := ret.LastInsertId() //新插入数据的ID,默认为主键
	//rowsNumber, err:= ret.RowsAffected() //受影响的行数
	if err != nil {
		fmt.Println("获取id失败,err", err)
		return
	}
	fmt.Println("插入成功,id为:", newID)
}

3.4 更新数据

package main

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

var (
	// 定义一个全局对象db
	 db *sql.DB
	//定义数据库连接的相关参数值
	//连接数据库的用户名
	userName  string = "root"
	//连接数据库的密码
	password  string = "admin"
	//连接数据库的地址
	ipAddress string = "127.0.0.1"
	//连接数据库的端口号
	port      int    = 3306
	//连接数据库的具体数据库名称
	dbName    string = "go_test"
	//连接数据库的编码格式
	charset   string = "utf8"
)

func initDB() (err error) {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddress, port, dbName, charset)
	//Open打开一个driverName指定的数据库,dataSourceName指定数据源
	//不会校验用户名和密码是否正确,只会对dsn的格式进行检测
	db, err = sql.Open("mysql", dsn)
	if err != nil { //dsn格式不正确的时候会报错
		return err
	}
	//尝试与数据库连接,校验dsn是否正确
	err = db.Ping()
	if err != nil {
		fmt.Println("校验失败,err", err)
		return err
	}
	// 设置最大连接数
	db.SetMaxOpenConns(50)
	// 设置最大的空闲连接数
	// db.SetMaxIdleConns(20)
	fmt.Println("连接数据库成功!")
	return nil
}

//创建一个和数据库表结构一样的机构体
type user struct {
	id   int
	name string
	age  int
}
func main() {
	err := initDB()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//需要修改的数据库对应记录的user结构体
	updateUser := user{
		id:   7,
		name: "蜡笔小新",
		age:  98}
	updateRow(updateUser)
}

// 更新数据
// updateUser   ----需要更新的user结构体
func updateRow(updateUser user) {
	sqlStr := "update user set age=?,name=? where id = ?"
	ret, err := db.Exec(sqlStr, updateUser.age, updateUser.name, updateUser.id)
	if err != nil {
		fmt.Printf("更新失败,err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected() // 操作影响的行数
	if err != nil {
		fmt.Printf("获取影响行数失败,err:%v\n", err)
		return
	}
	fmt.Printf("更新成功,影响行数为:%d\n", n)
}

3.5 删除数据

package main

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

var (
	// 定义一个全局对象db
	 db *sql.DB
	//定义数据库连接的相关参数值
	//连接数据库的用户名
	userName  string = "root"
	//连接数据库的密码
	password  string = "admin"
	//连接数据库的地址
	ipAddress string = "127.0.0.1"
	//连接数据库的端口号
	port      int    = 3306
	//连接数据库的具体数据库名称
	dbName    string = "go_test"
	//连接数据库的编码格式
	charset   string = "utf8"
)

func initDB() (err error) {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddress, port, dbName, charset)
	//Open打开一个driverName指定的数据库,dataSourceName指定数据源
	//不会校验用户名和密码是否正确,只会对dsn的格式进行检测
	db, err = sql.Open("mysql", dsn)
	if err != nil { //dsn格式不正确的时候会报错
		return err
	}
	//尝试与数据库连接,校验dsn是否正确
	err = db.Ping()
	if err != nil {
		fmt.Println("校验失败,err", err)
		return err
	}
	// 设置最大连接数
	db.SetMaxOpenConns(50)
	// 设置最大的空闲连接数
	// db.SetMaxIdleConns(20)
	fmt.Println("连接数据库成功!")
	return nil
}

//创建一个和数据库表结构一样的机构体
type user struct {
	id   int
	name string
	age  int
}
func main() {
	err := initDB()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//需要修改的数据库对应记录的user结构体,id不能为空
	deleteUser := user{
		id:   6,
		name: "蜡笔小新",
		age:  98}
	deleteRow(deleteUser)
}

// 删除数据
// deleteUser   ----需要删除的user结构体,删除的条件还可以是 age name等等
func deleteRow(deleteUser user) {
	sqlStr := "DELETE FROM user WHERE 1=1 AND  id = ?"
	ret, err := db.Exec(sqlStr, deleteUser.id)
	if err != nil {
		fmt.Printf("删除数据失败,err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected() // 操作影响的行数
	if err != nil {
		fmt.Printf("获取影响行数失败,err:%v\n", err)
		return
	}
	fmt.Printf("删除数据成功,影响行数为:%d\n", n)
}

3.6 查询数据

3.6.1 单行查询

单行查询db.QueryRow()执行一次查询,并期望返回最多一行结果(即Row)。语法如下:

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

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

var (
	// 定义一个全局对象db
	 db *sql.DB
	//定义数据库连接的相关参数值
	//连接数据库的用户名
	userName  string = "root"
	//连接数据库的密码
	password  string = "admin"
	//连接数据库的地址
	ipAddress string = "127.0.0.1"
	//连接数据库的端口号
	port      int    = 3306
	//连接数据库的具体数据库名称
	dbName    string = "go_test"
	//连接数据库的编码格式
	charset   string = "utf8"
)

func initDB() (err error) {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddress, port, dbName, charset)
	//Open打开一个driverName指定的数据库,dataSourceName指定数据源
	//不会校验用户名和密码是否正确,只会对dsn的格式进行检测
	db, err = sql.Open("mysql", dsn)
	if err != nil { //dsn格式不正确的时候会报错
		return err
	}
	//尝试与数据库连接,校验dsn是否正确
	err = db.Ping()
	if err != nil {
		fmt.Println("校验失败,err", err)
		return err
	}
	// 设置最大连接数
	db.SetMaxOpenConns(50)
	// 设置最大的空闲连接数
	// db.SetMaxIdleConns(20)
	fmt.Println("连接数据库成功!")
	return nil
}

//创建一个和数据库表结构一样的机构体
type user struct {
	id   int
	name string
	age  int
}
func main() {
	err := initDB()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//需要修改的数据库对应记录的user结构体,id不能为空
	queryUser := user{
		id:   3}
	QueryRow(queryUser)
}
//查询数据
func QueryRow(queryUser user)  {
	sqlStr := "SELECT id,name,age from user WHERE 1=1 AND  id = ?"
	row := db.QueryRow(sqlStr, queryUser.id)
	var u user
	//然后使用Scan()方法给对应类型变量赋值,以便取出结果,注意传入的是指针
	err:=row.Scan(&u.id, &u.name, &u.age)
	if err != nil {
		fmt.Printf("获取数据错误, err:%v\n", err)
		return
	}
	fmt.Printf("查询数据成功%#v",u)
}

3.6.2 多行查询

多行查询db.Query()执行一次查询,返回多行结果(即sql.Rows类型的结果集), 迭代后者使用Next()方法,然后使用Scan()方法给对应类型变量赋值,以便取出结果,最后再把结果集关闭(释放连接)语法如下:

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

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

var (
	// 定义一个全局对象db
	db *sql.DB
	//定义数据库连接的相关参数值
	//连接数据库的用户名
	userName  string = "root"
	//连接数据库的密码
	password  string = "admin"
	//连接数据库的地址
	ipAddress string = "127.0.0.1"
	//连接数据库的端口号
	port      int    = 3306
	//连接数据库的具体数据库名称
	dbName    string = "go_test"
	//连接数据库的编码格式
	charset   string = "utf8"
)

func initDB() (err error) {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddress, port, dbName, charset)
	//Open打开一个driverName指定的数据库,dataSourceName指定数据源
	//不会校验用户名和密码是否正确,只会对dsn的格式进行检测
	db, err = sql.Open("mysql", dsn)
	if err != nil { //dsn格式不正确的时候会报错
		return err
	}
	//尝试与数据库连接,校验dsn是否正确
	err = db.Ping()
	if err != nil {
		fmt.Println("校验失败,err", err)
		return err
	}
	// 设置最大连接数
	db.SetMaxOpenConns(50)
	// 设置最大的空闲连接数
	// db.SetMaxIdleConns(20)
	fmt.Println("连接数据库成功!")
	return nil
}

//创建一个和数据库表结构一样的机构体
type user struct {
	id   int
	name string
	age  int
}
func main() {
	err := initDB()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}

	queryRows()
}
//多行查询
func queryRows()  {
	sqlStr := "select id,name,age from user where id>?"
	rows,err := db.Query(sqlStr,0)
	if err != nil{
		fmt.Println("查询失败,err",err)
		return
	}
	defer rows.Close()  //关闭连接
	//循环读取数据
	for rows.Next(){
		var u user
		err := rows.Scan(&u.id,&u.name,&u.age)
		if err != nil{
			fmt.Println("scan失败,err",err)
			return
		}
		fmt.Printf("id:%d	name:%s		age:%d\n", u.id, u.name, u.age)
	}
}

4 MySQL预处理

4.1 什么是预处理?

::: tip 普通SQL语句执行过程

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

4.2 为什么要预处理?

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

4.3 MySQL预处理

Prepare方法会先将sql语句发送给MySQL服务端,返回一个准备好的状态用于之后的查询和命令。返回值可以同时执行多个查询和命令。

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

4.3.1 查询预处理

package main

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

var (
	// 定义一个全局对象db
	db *sql.DB
	//定义数据库连接的相关参数值
	//连接数据库的用户名
	userName  string = "root"
	//连接数据库的密码
	password  string = "admin"
	//连接数据库的地址
	ipAddress string = "127.0.0.1"
	//连接数据库的端口号
	port      int    = 3306
	//连接数据库的具体数据库名称
	dbName    string = "go_test"
	//连接数据库的编码格式
	charset   string = "utf8"
)

func initDB() (err error) {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddress, port, dbName, charset)
	//Open打开一个driverName指定的数据库,dataSourceName指定数据源
	//不会校验用户名和密码是否正确,只会对dsn的格式进行检测
	db, err = sql.Open("mysql", dsn)
	if err != nil { //dsn格式不正确的时候会报错
		return err
	}
	//尝试与数据库连接,校验dsn是否正确
	err = db.Ping()
	if err != nil {
		fmt.Println("校验失败,err", err)
		return err
	}
	// 设置最大连接数
	db.SetMaxOpenConns(50)
	// 设置最大的空闲连接数
	// db.SetMaxIdleConns(20)
	fmt.Println("连接数据库成功!")
	return nil
}

//创建一个和数据库表结构一样的机构体
type user struct {
	id   int
	name string
	age  int
}
func main() {
	err := initDB()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}

	prepareQueryRow()
}
//查询预处理
func prepareQueryRow()  {
	sqlStr := "select id,name,age from user where id > ?"
	stmt,err := db.Prepare(sqlStr)
	if err != nil{
		fmt.Println("预处理失败,err",err)
		return
	}
	defer stmt.Close()
	rows,err := stmt.Query(0)
	if err != nil{
		fmt.Println("查询失败,err",err)
		return
	}
	defer rows.Close()
	//循环读取
	for rows.Next(){
		var u user
		err := rows.Scan(&u.id,&u.name,&u.age)
		if err != nil{
			fmt.Println("scan失败,err",err)
			return
		}
		fmt.Printf("id:%d	 name:%s	 age:%d\n", u.id,u.name,u.age)
	}
}

4.3.2 增删改预处理

插入、更新和删除操作的预处理十分类似,这里以插入操作的预处理为例:

package main

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

var (
	// 定义一个全局对象db
	db *sql.DB
	//定义数据库连接的相关参数值
	//连接数据库的用户名
	userName  string = "root"
	//连接数据库的密码
	password  string = "admin"
	//连接数据库的地址
	ipAddress string = "127.0.0.1"
	//连接数据库的端口号
	port      int    = 3306
	//连接数据库的具体数据库名称
	dbName    string = "go_test"
	//连接数据库的编码格式
	charset   string = "utf8"
)

func initDB() (err error) {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddress, port, dbName, charset)
	//Open打开一个driverName指定的数据库,dataSourceName指定数据源
	//不会校验用户名和密码是否正确,只会对dsn的格式进行检测
	db, err = sql.Open("mysql", dsn)
	if err != nil { //dsn格式不正确的时候会报错
		return err
	}
	//尝试与数据库连接,校验dsn是否正确
	err = db.Ping()
	if err != nil {
		fmt.Println("校验失败,err", err)
		return err
	}
	// 设置最大连接数
	db.SetMaxOpenConns(50)
	// 设置最大的空闲连接数
	// db.SetMaxIdleConns(20)
	fmt.Println("连接数据库成功!")
	return nil
}

//创建一个和数据库表结构一样的机构体
type user struct {
	id   int
	name string
	age  int
}
func main() {
	err := initDB()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}

	prepareInsertDemo()
}
//批量插入
func prepareInsertDemo() {
	sqlStr := "insert into user (name,age) values(?,?)"
	stmt, err := db.Prepare(sqlStr) // 把要执行的命令发送给MySQL服务端做预处理
	if err != nil {
		fmt.Printf("预处理失败, err:%v\n", err)
		return
	}
	defer stmt.Close()
	// 执行重复的插入命令
	for i := 10; i < 15; i++ {
		name := fmt.Sprintf("name%02d", i)
		stmt.Exec(name, i)
	}
	fmt.Println("批量插入成功")
}

5 MySQL事务

5.1 什么是事务?

事务:一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元),同时这个完整的业务需要执行多次的DML(insert、update、delete)语句共同联合完成。A转账给B,这里面就需要执行两次update操作。 在MySQL中只有使用了Innodb数据库引擎的数据库或表才支持事务。事务处理可以用来维护数据库的完整性,保证成批的SQL语句要么全部执行,要么全部不执行。

5.2 事务的ACID

通常事务必须满足4个条件(ACID):原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。

条件解释
原子性一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
隔离性数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
持久性事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

5.3 事务相关方法

Go语言中使用以下三个方法实现MySQL中的事务操作。

//开始事务
func (db *DB) Begin() (*Tx, error)

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

//回滚事务
 func (tx *Tx) Rollback() error

5.4 事务举例

package main

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

var (
	// 定义一个全局对象db
	db *sql.DB
	//定义数据库连接的相关参数值
	//连接数据库的用户名
	userName  string = "root"
	//连接数据库的密码
	password  string = "admin"
	//连接数据库的地址
	ipAddress string = "127.0.0.1"
	//连接数据库的端口号
	port      int    = 3306
	//连接数据库的具体数据库名称
	dbName    string = "go_test"
	//连接数据库的编码格式
	charset   string = "utf8"
)

func initDB() (err error) {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddress, port, dbName, charset)
	//Open打开一个driverName指定的数据库,dataSourceName指定数据源
	//不会校验用户名和密码是否正确,只会对dsn的格式进行检测
	db, err = sql.Open("mysql", dsn)
	if err != nil { //dsn格式不正确的时候会报错
		return err
	}
	//尝试与数据库连接,校验dsn是否正确
	err = db.Ping()
	if err != nil {
		fmt.Println("校验失败,err", err)
		return err
	}
	// 设置最大连接数
	db.SetMaxOpenConns(50)
	// 设置最大的空闲连接数
	// db.SetMaxIdleConns(20)
	fmt.Println("连接数据库成功!")
	return nil
}

//创建一个和数据库表结构一样的机构体
type user struct {
	id   int
	name string
	age  int
}
func main() {
	err := initDB()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}

	transDemo()
}


func transDemo()  {
	tx,err := db.Begin()
	if err != nil{
		if tx != nil {
			tx.Rollback() // 回滚
		}
		fmt.Println("事务开启失败,err",err)
		return
	}
	sql1 := "update user set age=age+? where id=?"
	_,err = tx.Exec(sql1,2,1)
	if err != nil{
		tx.Rollback()
		fmt.Println("sql1执行失败,err",err)
		return
	}
	sql2 := "update user set age=age-? where id=?"
	_,err = tx.Exec(sql2,2,2)
	if err != nil{
		tx.Rollback()
		fmt.Println("sql2执行失败,err",err)
		return
	}
	err = tx.Commit()
	if err != nil{
		tx.Rollback()
		fmt.Println("事务提交失败,err",err)
		return
	}
	fmt.Println("数据更新成功!")
}

6 ORM框架学习

第三方库gorm能够简化操作,提高开发效率。特别是对结构体的应用

开始学习GORM框架