MySQL事务
事务是一组操作的集合,这一组操作具有原子性,事务会保证这一组操作要么全部执行成功,要么全部失败,也就是如果事务的操作中有一步操作出现异常,事务执行失败会将所有操作进行回滚。
典型的使用场景就是转账操作,张三给李四转1000块钱,转账这个操作分为三步:
- 先查询张三账户余额
- 张三账户扣除1000块
- 接着给李四账户加1000块
那么这两个操作要么同时执行成功,要么就都失败。
1. 事务四大特性(ACID)
- 原子性(Atomicity):原子是不可分割的最小单元,而事务的原子性指的就是要么全部成功,要么全部失败
- 一致性(Consistency):事务完成时,必须保证所有数据处于一致状态,比如说转账操作,张三和李四之间进行转账操作前他们的总余额为10000,转账操作后也必须是10000
- 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境运行,也就是事务和事务并发操作具有隔离性
- 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变时永久的
2. 操作事务
默认MySQL的事务是自动提交的,也就是说,当执行完一条语句时,MySQL会立即隐式的提交事务。通过start transaction开启事务,我们就可以手动提交和回滚事务了。
开启事务
start transaction;
提交事务
commit;
回滚事务
rollback
准备数据
create table balance
(
id int primary key auto_increment comment '用户id',
name varchar(16) comment '用户名',
money decimal(10,2) comment '账户余额'
);
insert into balance (name,money) values('张三',5000),('李四',5000);
测试事务回滚,在转账过程中如果发生异常就可以回滚事务,如果没有发生任何异常就可以直接提交事务。
模拟转账:
-- 开始事务
start transaction;
-- 查询账户余额
select * from balance;
+----+--------+---------+
| id | name | money |
+----+--------+---------+
| 1 | 张三 | 5000.00 |
| 2 | 李四 | 5000.00 |
+----+--------+---------+
-- 张三账户余额减少
update balance set money=money-1000 where name='张三';
-- 李四账户余额增加
mysql> update balance set money=money+1000 where name='李四';
-- 全部执行成功提交事务
commit;
-- 如果执行失败则回滚事务
-- rollback;
3. 并发事务问题
在并发场景下事务会出现以下问题:
-
脏读:一个事务读到另外一个事务还没有提交的数据,一般针对的是一条数据
以张三给李四转账1000为例:
- 假设张三账户里有5000块钱,张三转账这个操作先开启了事务A
- 接着对张三账户的余额修改为4000,此时事务A还没有提交
- 接着另外一个事务B又来查询张三的余额,查询完毕后此时张三的余额为4000
- 同时给李四余额进行增加时发送了异常,此时整个事务A进行了回滚
- 那么此时张三的账户余额还是5000,而另外一个事务B读取到的则是4000这个“脏数据”
-
不可重复读:一个事务先后读取同一条记录,当两次读取的数据不同,也是针对的一条数据
- 假设事务A第一先去查询张三的账户余额,发现是5000
- 而同时事务B把张三的余额修改成功了4000,同时提交了事务
- 紧接着事务A又进行了一次查询,发现此时张三的账户余额为4000
- 同一个事务两次查询同一份数据出现了不一样的结果这就是不可重复读
- 幻读:⼀个事务两次查询中得到的结果集不同,因为在两次查询中另⼀个事务有新增了⼀部分数据 ,好像出现"幻觉",而幻读一般是针对表级别的
- 事务A去查询用户表中有没有有一个叫王五的用户,发现没有
- 与此同时事务B往用户表里插入了一个叫王五的用户,并且提交了事务
- 接着事务A也插入一个王五的用户,发现插入失败
- 一个事务查询时发现这个数据不存在,而插入时这个数据又出现了,这就是幻读
4. 事务的隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(Read uncommitted) | 存在 | 存在 | 存在 |
读已提交(Read committed) | 不存在 | 存在 | 存在 |
可重复读(Repeatable Read(默认)) | 不存在 | 不存在 | 存在 |
串行化(Serializable) | 不存在 | 不存在 | 不存在 |
事务的隔离级别越高,数据越安全,但是性能也越低的
MySQL5.X查看隔离级别:
select @@global.tx_isolation;
设置事务隔离级别
SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED |
READ COMMITTED | REPEATABLE READ | SERIALIZABLE }
读未提交
-- 设置隔离级别为读未提交
set global transaction isolation level Read uncommitted;
测试脏读
此时的隔离级别为读未提交
读已提交
测试脏读
不存在脏读问题
不可重复读
显然读已提交存在不可重复读问题
可重复读
测试不可重复读问题,不存在该问题
串行化
测试幻读,发现B事务还没提交A事务同事插入了同一条数据,事务A直接阻塞了,所以不存在幻读问题