一.事务概念:
- 所谓数据库事务,是指作为单个逻辑工作单元执行的一系列操作要么都执行,要么都不执行,简单的说,事务就是一堆SQL语句的执行绑定在一起,要么都执行成功,要么都执行失败,即都执行成功才算成功,否则就会恢复到这堆SQL语句执行前的状态
- 我们以银行转账为例:张三给李四转了100元
1)给张三账户上减去100
update 帐户表 set money=money-100 where name='张三';
2)给李四账户上添加100元
update 账户表 set money=money+100 where name='李四';
如果说第一条语句执行成功了,而执行第二条语句之前由于程序中断,抛出了一个异常导致第二条语句没有执行成功,那么李四账户上没有添加成功,那么张三账户上也不可能减少100,这样也就更好的理解了事务
二.事务的四大特征
事务有四大特性是:
1)原子性:事务中所有的操作是不可在分的原子单位,事务中所有的操作要么都执行成功,要么都执行失败
2)一致性:事务执行前后,数据库状态应保持一致,如转账业务,无论事务执行成功与否,参与转账的两个账号金额之和应保持一致
3)隔离性:两个事务的操作是完全隔离的,双方是看不到彼此的中间执行状态,两者互不干扰,也就是说,在事务中查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改之后的状态,事务不会查看到中间状态的数据,如:在A事务中,查看另一B事务(正在修改张三的账户金额)中张三的账户金额,要么查看到B事务之前的张三的账户金额,要么查看到B事务之后张三的账户金额。
4)持久性:一旦事务提交成功后,事务中所有的数据操作都必须持久的保存数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据
三.事务并发读问题
所谓事务并发,就是多个事务对相同数据同时进行操作
在事务并发时,如果没有采取必要的隔离措施,可能会导致各种并发问题,破坏数据的完整性,这些问题中,其中三类是读问题,分别是:脏读,不可重复读,幻读
我们分别详解三类读问题:
- 脏读:一个事务中读到另一个事务未提交的数据
例如:A给B转账100元但未提交事务,在B查询之后,A做了回滚操作,那么B查询到A未提交的数据
-- 在窗口1中,开启事务,执行A给B转账100元
set tx_isolation='read-uncommitted'; -- 允许脏读、不可重复读、幻读
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
update acc set money=money-100 where name='A';
update acc set money=money+100 where name='B';
-- 在窗口2中,开启事务,查询B的账户金额
set tx_isolation='read-uncommitted'; -- 允许脏读、不可重复读、幻读
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
select * from acc where name= 'B'; -- 出现脏数据
-- 切换到窗口1,回滚事务,撤销转账操作。
rollback; -- 回滚事务
-- 切换到窗口2,查询B的账户金额
select * from acc where name='B';
在窗口2中,B看到自己的账户增加了100元(此时的数据A操作事务并未提交),此种情况称为“脏读”
- 不可重复读:对同一记录的两次读取不一致,因为另一事务对该记录做了修改,在一个事务中读到另一个事务已经提交的数据
例如:在事务1中,前后两次查询A账户的金额,在两次查询之间,另一事务2对A账户的金额做了修改,此种情况下可能会导致事务1中,前后两次查询结果不一致
-- 在窗口1中,开启事务,查询A账户的金额
set tx_isolation='read-uncommitted'; -- 允许脏读、不可重复读、幻读
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
select * from acc where name='A';
-- 在窗口2中,开启事务,查询A的账户金额减100
set tx_isolation='read-uncommitted'; -- 允许脏读、不可重复读、幻读
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
update acc set money=money-100 where name='A'; -- A账户减去100
select * from acc where name='A';
commit; -- 提交事务
-- 切换到窗口1,再次查询A账户的金额。
select * from acc where name='A'; -- 前后查询结果不一致
在窗口1中,前后两次对同一数据(账户A的金额)查询结果不一致,是因为在两次查询之间,另一事务对该A账户的金额做了修改
- 幻读:对同一张表的两次查询不一致,是因为另一事务插入了一条记录(针对插入或删除操作)
-- 在窗口1中,开启事务,查询账户表中是否存在id=3的账户
set tx_isolation='read-uncommitted'; -- 允许脏读、不可重复读、幻读
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
select * from acc where id=3;
-- 在窗口2中,开启事务,往账户表中插入了一条id为3记录,并提交事务。
-- 设置mysql允许出现脏读、不可重复度、幻读
set tx_isolation='read-uncommitted';
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
insert into acc values(3, 'C', 1000);
commit; -- 提交事务
-- 切换到窗口1,由于上面窗口1中查询到没有id为3的记录,所以可以插入id为3的记录。
insert into acc values(3, 'C', 1000); -- 插入会失败!
在窗口1中,查询了不存在id为3的记录,所以接下来要执行插入id为3的记录,但是还未执行插入时,另一事务中插入了id为3的记录并提交了事务,所以接下来窗口1中执行插入操作会失败。
探究原因,发现账户表中又有了id为3的记录(感觉像是出现了幻觉)。这种情况称之为"幻读
四.事务隔离级别
针对以上所说的并发事务的读问题,事务设置了隔离级别,在相同环境下,对数据执行相同的操作,设置不同的隔离级别,可能会导致不同的结果,不同事务隔离级别能够解决的数据并发问题的能力也是不同的
- RED UNCOMMITTED(读未提交数据)
该级别是安全级别最低,可能出现任何事务并发问题(比如:脏读,不可重复读,幻读),但是性能最好
set tx_isolation='read-uncommitted';
- RED COMMITTED(读已提交数据)
该级别是为了防止脏读,没有处理不可重复读,也没有处理幻读
set tx_isolation='read-committed';
- REPEATABLE RED(不可重复读)
该级别是防止脏读和不可重复读,不能处理幻读
set tx_isolation='repeatable-read';在这里插入代码片
- SERIALIZABLE(串行化)
不会出现任何并发问题,因为它是对同一数据的访问是串行的,非并发访问的,性能最差,但是安全性最高
set tx_isolation='serialiable';