Go 操作 Mysql(三)
什么是事务?
事务是数据库非常重要的部分,它具有四大特性(原子性、一致性、隔离性、持久性)
以下内容出自《高性能MySQL》第三版,了解事务的ACID及四种隔离级有助于我们更好的理解事务运作。
下面举一个银行应用是解释事务必要性的一个经典例子。假如一个银行的数据库有两张表:支票表(checking)和储蓄表(savings)。现在要从用户Jane的支票账户转移200美元到她的储蓄账户,那么至少需要三个步骤:
- 检查支票账户的余额高于或者等于200美元。
- 从支票账户余额中减去200美元。
- 在储蓄帐户余额中增加200美元。
上述三个步骤的操作必须打包在一个事务中,任何一个步骤失败,则必须回滚所有的步骤。
一个很好的事务处理系统,必须具备这些标准特性:
- 原子性(atomicity)
一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性
- 一致性(consistency)
数据库总是从一个一致性的状态转换到另一个一致性的状态。(在前面的例子中,一致性确保了,即使在执行第三、四条语句之间时系统崩溃,支票账户中也不会损失200美元,因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中。)
- 隔离性(isolation)
通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。(在前面的例子中,当执行完第三条语句、第四条语句还未开始时,此时有另外的一个账户汇总程序开始运行,则其看到支票帐户的余额并没有被减去200美元。)
- 持久性(durability)
一旦事务提交,则其所做的修改会永久保存到数据库。(此时即使系统崩溃,修改的数据也不会丢失。)
Tx 对象
database/sql 提供了事务处理的功能,通过 Tx 类型实现
之前在增删改查操作中使用的都是 sqlx.DB 类型对象,而事务则使用的是 Tx 类型对象,使用 DB.Begin() 方法可以创建 Tx 类型对象,Tx 类型对象也可以调用 Exec() 和 Query() 等方法执行数据库操作,用法和之前操作一样,但是需要在操作完毕后执行 Tx 的 Commit() 或 Rollback() 方法完成数据库事务的提交或回滚,同时释放连接
一旦调用 Begin() 方法,Tx 类型对象就会从连接池中获取一个空闲的连接,接下来的 sql 执行都基于这个连接,直到 commit 或 rollback 后才释放到连接池
Tx 类型对象拥有的方法如下:
在事务处理的时候,不能使用 DB 类型对象的方法去执行 sql 语句,当然如果使用也能执行成功,但是这和你在事务里执行的操作不属于一个事务(隔离性),将不会接受 commit 或 rollback 的改变,如下面的操作时:
demo:下面这个伪代码中,调用Db.Exec() 方法的时候,和 Tx 执行 Exec() 方法是不同的,只有 Tx 的操作会绑定到事务中,Db 则是额外的一个连接,两者不属于同一个事务
Tx,err := Db.Begin()
Db.Exec()
Tx.Exec()
Tx.Commit()
事务并发
对于 sqlx.DB 类型对象,调用了 Query() 方法之后,在 Next() 方法中获取结果的时候,rows 是维护了一个连接,再次调用 QueryRow() 方法的时候,DB 类型对象会再从连接池取出一个新的连接给到查询结果 row,row 和 rows 的连接,两者可以共存,并且互相不影响
for rows.Next() {
//定义变量接收查询数据
var uid int
var create_time, username, password, department, email string err := rows.Scan(&uid, &create_time, &username, &password, &department, &email)
if err != nil {
fmt.Println("get data failed, error:[%v]", err.Error())
}
fmt.Println(uid, create_time, username, password, department, email) row := Db.QueryRow("select * from userinfo where uid = 1")
err = row.Scan(&uid, &create_time, &username, &password, &department, &email)
if err != nil {
fmt.Printf("scan failed, error:[%v]", err.Error())
return
}
fmt.Println(uid, create_time, username, password, department, email)
} //关闭结果集(释放连接)
rows.Close() 运行结果:
1 2019-07-06 11:45:20 anson 123456 技术部 123456@163.com
1 2019-07-06 11:45:20 anson 123456 技术部 123456@163.com
3 2019-07-06 11:45:20 johny 123456 技术部 123456@163.com
1 2019-07-06 11:45:20 anson 123456 技术部 123456@163.com
4 2019-07-06 11:45:20 johny 123456 技术部 123456@163.com
1 2019-07-06 11:45:20 anson 123456 技术部 123456@163.com
对于 sql.Tx 类型对象,因为事务过程只有一个连接,事务内的操作都是顺序执行的,在开始下一个数据库交互之前,必须先完成上一个数据库交互
demo:Tx 执行了 Query() 方法后,rows 维护了数据库连接,然后 Tx 尝试调用 QueryRow 将尝试获取该连接进行数据库操作,因为还没有调用 rows.Close() 方法(事务还没有结束),因此连接属于 busy 状态,Tx 类型对象是无法再从连接池获取连接的
rows, err := Tx.Query("select * from userinfo")
if err != nil {
fmt.Printf("query faied, error:[%v]", err.Error())
return
}
for rows.Next() {
//定义变量接收查询数据
var uid int
var create_time, username, password, department, email string err := rows.Scan(&uid, &create_time, &username, &password, &department, &email)
if err != nil {
fmt.Println("get data failed, error:[%v]", err.Error())
}
fmt.Println(uid, create_time, username, password, department, email) row := Tx.QueryRow("select * from userinfo where uid = 1")
//在这里获取数据库连接报错
err = row.Scan(&uid, &create_time, &username, &password, &department, &email)
if err != nil {
fmt.Printf("scan failed, error:[%v]", err.Error())
return
}
fmt.Println(uid, create_time, username, password, department, email)
} //关闭结果集(释放连接)
rows.Close() 运行结果:
1 2019-07-06 11:45:20 anson 123456 技术部 123456@163.com
scan failed, error:[driver: bad connection][mysql] 2019/07/09 22:52:07 packets.go:446: busy buffer
实例(加强理解)
通过下面完整的例子能够更好的理解
demo:
- 定义了一个 clearTransaction(Tx) 函数,该函数会执行 rollback 操作,因为在事物处理的过程中,任何一个错误都会导致 main 函数退出,因此在 main 函数退出时执行 defer 的 rollback 操作,回滚事务和释放连接
- 如果不添加 defer 来回滚事务和释放连接,只在最后 commit 或 rollback,那么当 doSomething 发生异常的时候,函数就退出了,此时还没有执行到 commit 操作,这样就导致该事务的连接没有关闭,事务也没有回滚
- Tx 事务环境中,只有一个数据库连接,事务内的 Exec() 方法都是依次执行的,事务中也可以使用 DB 进行查询,但是 DB 查询的过程会新建连接,不属于 Tx 这个事务
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
) var (
userName string = "chenkai"
password string = "chenkai"
ipAddrees string = "192.168.0.115"
port int = 3306
dbName string = "test"
charset string = "utf8"
) func connectMysql() (*sqlx.DB) {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddrees, port, dbName, charset)
Db, err := sqlx.Open("mysql", dsn)
if err != nil {
fmt.Printf("mysql connect failed, detail is [%v]", err.Error())
}
return Db
} func updateData(Db *sqlx.DB) {
Tx, err := Db.Begin()
if err != nil {
fmt.Printf("open the transaction failed, error:[%v]", err.Error())
return
}
//回滚处理
defer clearTransaction(Tx) result, err := Tx.Exec("update userinfo set username = 'honey' where uid = 1")
if err != nil {
fmt.Printf("update failed, error:[%v]", err.Error())
return
}
rowAffected, _ := result.RowsAffected()
fmt.Printf("affected rows:[%v]\n", rowAffected) result, err = Tx.Exec("update userinfo set username = 'honey' where uid = 3")
if err != nil {
fmt.Printf("update failed, error:[%v]", err.Error())
return
}
rowAffected, _ = result.RowsAffected()
fmt.Printf("affected rows:[%v]\n", rowAffected) //主动 panic
doSomething() //提交事务
err = Tx.Commit()
if err != nil {
// tx.Rollback() 此时处理错误,会忽略 doSomthing 的异常
fmt.Printf("commit failed, error:[%v]", err.Error())
}
} func clearTransaction(Tx *sql.Tx) {
//尝试进行 rollback,若 Tx 已经关闭,则不作处理
err := Tx.Rollback()
if err != sql.ErrTxDone && err != nil {
fmt.Printf("tx rollback failed, error:[%v]", err.Error())
}
} func doSomething(){
panic("a panic running error")
} func main() {
var Db *sqlx.DB = connectMysql()
defer Db.Close() updateData(Db)
} 运行结果:
affected rows:[1]
affected rows:[1]
panic: a panic running error
ending ~
Go 操作 Mysql(三)的更多相关文章
- Go基础之--操作Mysql(三)
事务是数据库的一个非常重要的特性,尤其对于银行,支付系统,等等.database/sql提供了事务处理的功能.通过Tx对象实现.db.Begin会创建tx对象,后者的Exec和Query执行事务的数据 ...
- python对mysql数据库操作的三种不同方式
首先要说一下,在这个暑期如果没有什么特殊情况,我打算用python尝试写一个考试系统,希望能在下学期的python课程实际使用,并且尽量在此之前把用到的相关技术都以分篇博客的方式分享出来,有想要交流的 ...
- mysql(三) 数据表的基本操作操作
mysql(三) 数据表的基本操作操作 创建表,曾删改查,主键,外键,基本数据类型. 1. 创建表 create table 表名( 列名 类型 是否可以为空, 列名 类型 是否可以为空 )ENGIN ...
- 一、初识MySQL数据库 二、搭建MySQL数据库(重点) 三、使用MySQL数据库 四、认识MySQL数据库的数据类型 五、操作MySQL数据库的数据(重点)
一.初识MySQL数据库 ###<1>数据库概述 1. 数据库 长期存储在计算机内的,由组织的可共享的数据集合 存储数据的仓库 文件 ...
- php三种方式操作mysql数据库
php可以通过三种方式操作数据库,分别用mysql扩展库,mysqli扩展库,和mysqli的预处理模式分别举案例加以说明 1.通过mysql方式操作数据库 工具类核心代码: <?php cla ...
- python接口自动化(三十八)-python操作mysql数据库(详解)
简介 现在的招聘要求对QA人员的要求越来越高,测试的一些基础知识就不必说了,来说测试知识以外的,会不会一门或者多门开发与语言,能不能读懂代码,会不会Linux,会不会搭建测试系统,会不会常用的数据库, ...
- jdbc操作mysql(三):利用注解封装
案例五:利用注解封装 重复步骤 我们使用jdbc操作mysql时发现,操作不同表中数据,所写的方法基本相同:比如我们根据id向用户表添加数据,根据id删除商品表的数据,或者查询所有数据并用list集合 ...
- Python(九) Python 操作 MySQL 之 pysql 与 SQLAchemy
本文针对 Python 操作 MySQL 主要使用的两种方式讲解: 原生模块 pymsql ORM框架 SQLAchemy 本章内容: pymsql 执行 sql 增\删\改\查 语句 pymsql ...
- Python中操作mysql的pymysql模块详解
Python中操作mysql的pymysql模块详解 前言 pymsql是Python中操作MySQL的模块,其使用方法和MySQLdb几乎相同.但目前pymysql支持python3.x而后者不支持 ...
- MySQL(三)
MYSQL(三) 上一章给大家说的是数据库的视图,存储过程等等操作,这章主要讲索引,以及索引注意事项,如果想看前面的文章,url如下: MYSQL入门全套(第一部) MYSQL入门全套(第二部) 索引 ...
随机推荐
- c语言字符串分割函数(转)
源:C语言实现split以某个字符分割一个字符串 void split(char *src, const char *separator, char **dest, int *num) { /* sr ...
- Flutter StatefulWidget 有状态组件、页面上绑定数据、改变页面数据
在 Flutter 中自定义组件其实就是一个类,这个类需要继承 StatelessWidget/StatefulWidget. StatelessWidget 是无状态组件,状态不可变的 widget ...
- 【转】Django继承AbstractUser新建User Model时出现auth.User.groups: (fields.E304)错误
错误详情如下: (venv) D:\workspace\music>python manage.py makemigrations SystemCheckError: System check ...
- ISO/IEC 9899:2011 条款6.5.8——关系操作符
6.5.8 关系操作符 语法 1.relational-expression: shift-expression relational-expression < shift-expr ...
- 三、HTTP响应
HTTP消息是服务器和客户端之间交换数据的方式 有两种类型的消息: 请求--由客户端发送用来触发一个服务器上的动作 相应--来自服务器的应答 一.HTTP响应的构成 1.状态行 HTTP响应的起始行被 ...
- Centos7 手动编译 RabbitMQ ,并安装php amqp
RabbitMQ是一个在AMQP基础上完成的,可复用的企业消息系统,底层基于Erlang语言. 一:centos7安装RabbitMQ 这玩意儿安装很扯淡,官方推荐rpm安装,rpm安装本身是最简单的 ...
- 123457123456#1#----com.tym.DishuGame78--前拼后广--宝宝打地鼠_tym
com.tym.DishuGame78--前拼后广--宝宝打地鼠_tym
- c# Invoke的新用法
在C# 3.0及以后的版本中有了Lamda表达式,像上面这种匿名委托有了更简洁的写法..NET Framework 3.5及以后版本更能用Action封装方法.例如以下写法可以看上去非常简洁: voi ...
- Python - Django - 模板语言之 Filters(过滤器)
通过管道符 "|" 来使用过滤器,{{ value|过滤器:参数 }} Django 的模板语言中提供了六十个左右的内置过滤器 urls.py: from django.conf. ...
- angular2-cookie 如何升级到 ngx-cookie
angular2-cookie 如何升级到 ngx-cookie https://github.com/salemdar/angular2-cookie#readme