首先,什么是事务呢?
事务就是由单独单元的一个或多个sql语句组成, 在这个单元中, 每个sql的语句都是相互依赖的, 而整个单独单元作为一个不可分割的整体存在, 类似于物理当中的原子(一种不可分割的最小单位)
通俗的来讲就是, 事务就是一个整体, 里面的内容要么都执行成功, 要么都不成功, 不可能存在部分执行成功而部分执行不成功的情况
就是说如果单元中某条sql语句一旦执行失败或者产生错误, 那么整个单元将会回滚(返回最初状态)所有受到影响的数据将返回到事务开始之前的状态, 但是如果单元中的所有sql语句都执行成功的话, 那么该事务也就被顺利执行
大家都知道, 我们的数据就是通过各种不同的技术存储引擎来引导存储的, 不同的存储引擎, 都有各自的特点. 在mysql中,常用的存储引起有innodb, mysiam, memor等.其中innodb支持事务(transeaction), 而myisam, memory等不支持事务
可以通过
show engines;
语句来查看mysql支持的存储引擎
一 事务的四个特性(ACID)
- 原子性: 指事务是一个不可分割的最小工作单位, 事务中的操作只有都发生和都不发生的两种情况
- 一致性: 事务必须使数据库从一个一致状态变换到另外一个一致状态, 其他事务看到要么是最开始没转账的状态, 要么是转账成功后的状态, 忽略中间态
- 隔离性: 一个事务的执行不能被其它事务干扰, 并发执行的各个事物之间不能互相干扰
- 持久性: 一个事务一旦提交成功, 持久化到硬盘
二 事务的分类
事务分为隐式事务和显示事务两种 我们的dml语句(insert, update,delete)就是隐式事务
1. 隐式事务: 该事务没有明显的开启和结束标记, 它们都具有自动提交事务的功能; 不妨思考一下, update语句修改数据时, 是不是对表中的数据进行改变了, 它的本质其实就相当于一个事务
举例: 张三同学买了一个杯子99元, 是不是就是update语句对字段name为张三的同学的余额balance进行减99元的处理呢?代码如下
2. 显示事务: 该事务具有明显的开启和结束标记; 也是本文重点要将的东西 使用显示事务的前提是你得先把自动提交事务的功能给禁用, 禁用自动提交功能就是设置autocommit变量值为0(0:禁用 1:开启)
先查看下当前autocommit变量值, 发现当前处于开启自动提交事务的状态
禁用自动提交事务的功能并查看状态
三 开启事务的步骤
#步骤一:开启事务(可选)
start transaction;
#步骤二:编写事务中的sql语句(insert、update、delete)
#这里实现一下"李二给王五转账"的事务过程
update t_account set balance = 50 where vname = "李二";
update t_account set balance = 130 where vname = "王五";
#步骤三:结束事务
commit; #提交事务
# rollback; #回滚事务:就是事务不执行,回滚到事务执行前的状态
运行结果:
四. 事务并发时出现的问题
但是呢, 因为某一时刻不可能总只有一个事务在运行, 可能出现A在操作t_account表中的数据, B也同样在操作t_account表, 那么就会出现并发问题, 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采用必要的隔离机制, 就会发生以下各种并发问题
- 脏读: 对于两个事务t1, t2, t1读取了已经被t2更新但是没有被提交的字段后, 若t2回滚, t1读取的内容就是临时且无效的
- 不可重复读: 对于两个事务t1, t2, t1读取了一个字段, 然后t2更新了该字段, t1再读取同一个字段, 值就不一样了
- 幻读: 对于两个事务t1,t2, t1在A表中读取了一个字段, 然后t2又在A表中插入了一些新的数据时, t1再读取该表时, 就会发现神不知鬼不觉地多了几行了...
所以, 为了避免以上出现的各种问题, 我们就必须要采取一些手段, mysql数据库系统提供了4种事务的隔离级别, 用来隔离并发运行的各个事务, 是的它们相互不受影响, 这就是数据库事务的隔离性
五. 事务的隔离级别
mysql种的四种事务隔离级别如下:
1. read uncommitted(读未提交): 允许事务读取未被其他事务提交的数据 (最低级别)
2. read committed(读已提交): 只允许事务读取已经被它是事务提交的数据(可以避免脏读)
3. repeatable read(可重复读): 确保事务可以多次读取相同的行, 在这个事务持续期间, 禁止其他事务对这个字段修改(避免脏读, 不可重复读)
4. serializable(串行化): 确保事务可以多次读取相同的行, 在这个事务持续期间,禁止其他事务对这个表修改, 锁表性能十分低下
一个事务与其他事务隔离的程度称之为隔离级别. 数据库规定了多种事务隔离级别, 不同隔离几倍对应不同的干扰程度, 隔离级别越高, 数据的一致性就越好, 但并发性能就越差
这个通过一个例子简单介绍下并发:
一个人边开开车边打电话, 首先, 人只有一个大脑(cpu), 但是在同一时刻它却在执行2件事, 其实内部就是它的大脑在不同的切换, 之所以jc不允许司机边开车边打电话, 就是怕人脑在那一瞬间切换不过来, 从而导致交通事故的发生, 并发和这个例子是差不多的意思 但是这里, 电脑cpu可比人脑块多了, 所以出错的概率也相对来说小很多
接下来, 演示下, 集中不同事务隔离级别下发生的情况
在演示之前, 还需要知道如何查看和设置事务的隔离级别
语法:select @@tx_isolation;
#设置当前mysql连接的隔离级别:
set session transaction isolation level read uncommitted;
#设置数据库系统的全局的隔离级别:
set global transaction isolation level read uncommitted;
注意: 当前mysql连接的隔离级别和mysql全局的隔离级别的区别是什么?
如果只设置当前的隔离级别, 也就是session, 那么另外一个并发的session的隔离级别不受当前连接的影响, 也就是默认的repeatable read
但是如果是设置全局的事务隔离级别, 则整个mysql数据库(包括所有打开的session)的隔离级别都会随之改变, 除非服务重启, 不然就不会恢复默认
两者仅仅一词之差, 其效果却天差地别
好, 了解完如何设置事务的隔离级别之后, 下面将正式进入...呃呃呃, 等一下
这里的讲解狐妖是为了知道在并发情况下, 不同的事务隔离级别所表现出的不同特点, 那么自然还是要先模拟一下并发环境
正片开始, 同志们打起精神
1. read uncommitted(读未提交)
首先,我们需要先将两个会话的事务隔离级别都设置为read uncommitted;语句如下:
read uncommitted可以读到其它事务还没有提交的变更, 这里举例: 程序2对t_account表中的数据进行更改, 看程序1多次查询结果是否一致
可以看到程序2改变了t_account表中的vname字段, 将李二改为了张三, 但是程序1呢, 连续2个selelct查询语句的结果竟然不一致, 估计现在程序1的表情和你手机里面的第三个表情包一样, 这就是read uncommitted隔离级别的特点
不管你事务是否提交, 只要数据发生改变我就可以察觉到, 嘻嘻, 是不是感觉到很强大, 什么事情都逃不过它的法眼
接下来要看的是read committed(读已提交)
- read committed(读已提交)
测试前一定要记住设置事务的隔离级别为read committed; 并且禁用自动提交事务(设置autocommite=0)
# 设置事务隔离级别为read committed
set session transaction isolation level read committed;
# 禁用自动提交事务功能
set autocommit = 0;
#接下来的 repeatable read 隔离级别和 serializable 隔离级别也是同样的操作
以上例子实现了王五为张三转账的事务, 可以看到程序2中事务提交前与提交后对程序1中的查询语句产生的影响, 第一个查询事务没提交结果, 第二次查询是事务提交后的结果, 相信上面的例子已经诠释了read commited的特点
- repeatable read(可重复读)
该隔离级别为mysql的默认隔离级别, 它对某字段操作时, 其他事务禁止操作该字段, 它总能保持你读取的数据是一致的, 以下代码中, 程序1模拟"王五向张三转30元", 程序2则在程序1在处理事务时, 对张三的余额进行清空处理
程序1 操作t_account表时,程序2是无权对t_account表进行任何操作, 这里因为update语句会默认添加独占锁, 如果其他事务对特有的独占锁的记录进行修改时, 就会被阻塞
“ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction”;
其中文意思是:“超过锁定等待超时;尝试重新启动事务”
只有当程序1对t_account表操作结束后(结束事务后), 程序2才可以对t_account表进行操作
- serializable(串行化)
该隔离模式下执行的事务在对某表进行操作期间, 禁止其他所有事务对该表进行任何操作
如果强行操作了也会报错(和上面错误一样), 因为serializable用的相对比较少, 这里就不做演示了, 理解就好
- 事务的保存点(回滚点)
回滚点表示的就是使事务回滚到指定的回滚点
语法: savepoint 节点名称 ;
注意:保存点只允许搭配rollback回滚来使用,不能和commit一起使用
已知表t_stu存在, 其数据如下
代码举例如下:
#禁用自动提交事务
set autocommit = 0;
#开启事务
start transaction;
#删除id为2的记录
delete from t_stu where id = 2;
#设置保存点名为AA
savepoint AA;
#删除id为3的记录
delete from t_stu where id = 3;
#回滚到AA保存点处
rollback to AA;
可以看到id为2的李四被删除了, 而王五却还在, 就是因为事务回滚到了AA处, 所以为3的那条记录被回滚掉了