0. 前提概要
谈事务,一般就是说数据库事务。本篇文章以 MySQL 为例谈一谈事务。
MySQL 的 Indndb 引擎和 bdb 引擎支持事务。MySQL 的myisam ,memory 等存储引擎是不支持事务的。
自 MySQL5.5之后,默认的存储引擎是Innodb。
1. 什么是事务?
事务是逻辑上的一组操作,要么都执行,要么都不执行。
比如说我给你2块钱,你给我个外婆菜包子。这两个动作的执行结果是一致的,要么都成功,要么都失败。 不能说我给了你两块钱,你给我包子的动作却执行失败了,那这两个动作就不满足事务(那我早点吃什么~)。
2. 事务的四大特性
事务具体四大特性,也就是经常说的ACID ,常用的MySQL、SqlServer、Orancle都具备这四大特性。
- 原子性(Atomicity): 事务是最小的执行单位,不允许分割。事务的原⼦性确保动作要么全部完成,要么完全不起作用;
- ⼀致性(Consistency): 执行事务前后,数据保持⼀致,多个事务对同⼀个数据读取的结果是相同的;
- 隔离性(Isolation): 并发访问数据库时,⼀个⽤户的事务不被其他事务所⼲扰,各并发事务之间数据是独立的;
- 持久性(Durability): ⼀个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
MySQL 中的 ACID 实现原理: 原子性:undolog(记录事务开始前的老版本数据,可以保证原子操作,回滚,实现MVCC版本链) 隔离性:读写锁 、MVCC 持久性:redo log(记录事务开启后对数据的修改,可用于crash-safe)
3. 并发存在的问题
- 脏读:读到未提交的数
- 不可重复读:一个事务下,两次读取数据不一致(侧重内容数据的修改)
- 幻读:事务A 按照一定条件进行数据读取, 期间事务B 插入了相同搜索条件的新数据,事务A再次按照原先条件进行读取时,发现了事务B 新插入的数据 称为幻读(侧重新增或删除,插入数据读到多了一行)
不可重复读:破坏了一致性,update 和 delete 幻读:破坏了一致性 insert 不可重复读和幻读的区别 ● 不可重复读的重点是内容修改或者记录减少比如多次读取一条记录发现其中某些记录的值被修改; ● 幻读的重点在于记录新增比如多次执行同一条查询语句(DQL)时,发现查到的记录增加了。 幻读其实可以看作是不可重复读的一种特殊情况, 单独把区分幻读的原因主要是解决幻读和不可重复读的方案不一样。 举个例子:执行 delete 和 update 操作的时候,可以直接对记录加锁,保证事务安全。 而执行 insert 操作的时候,由于记录锁(Record Lock)只能锁住已经存在的记录,为了避免插入新记录, 需要依赖间隙锁(Gap Lock)。 也就是说执行 insert 操作的时候需要依赖 Next-Key Lock(Record Lock+Gap Lock) 进行加锁来保证不出现幻读。
4. SQL 标准定义的事务隔离级别
隔离级别原理及解决问题分析:
- 读未提交:原理:直接读取数据,不能解决任何并发问题
- 读已提交:读操作不加锁,写操作加排他锁,解决了脏读。原理:利用MVCC实现,每一句语句执行前都会生成Read View(一致性视图)
- 可重复读:MVCC实现,只有事务开始时会创建Read View,之后事务里的其他查询都用这个Read View。解决了脏读、不可重复读,快照读(普通查询,读取历史数据)使用MVCC解决了幻读,当前读(读取最新提交数据)通过间隙锁解决幻读(lock in share mode、for update、update、detete、insert),间隙锁在可重复读下才生效。(默认隔离级别)
- 可串行化:原理:使用锁,读加共享锁,写加排他锁,串行执行
总结: 读已提交和可重复读实现原理就是MVCC Read View不同的生成时机。 可重复读只在事务开始时生成一个Read View,之后都用的这个;读已提交每次执行前都会生成Read View。
MySQL 在 InnoDB下是默认可重复读的隔离级别,加上 MVCC机制 解决了脏读、不可重复读、幻读的问题。
5. SpringBoot 解决事务
5.1 编程式事务
Spring 提供一个接口(PlatfromTransactionManager) 代表事务管理器,为不同框架提供了不同的实现类。
Mybatis 框架 事务的实现类是 DataSourceTransactonManager
使用案例
@Autowired private TransactionDefinition transactionDefinition; @Autowired private DataSourceTransactionManager transactionManager; // 开启事务 TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition); try { // 业务操作 // ... // 提交事务 transactionManager.commit(transactionStatus); } catch (Exception e) { // 回滚事务 transactionManager.rollback(transactionStatus); log.error("..."); }
5.2 声明式事务
Spring 需要手动开启事务, SpringBoot 默认开启了 不用手动开启。
具体方法上加事务注解就行了。但是 注意事务失效情况。