一、事务
事务的定义:
事务指的是满足 ACID 特性的一组操作,可以通过 Commit 提交,也可以使用 Rollback 回滚。它的任务是在并发场景下为线程提供隔离,保持数据库一致性状态
事物的特性:
原子性、一致性、隔离性、持久性,这四个属性通常称为ACID特性。
1、原子性(Atomicity)
事务作为一个整体被执行,对数据库的操作要么全部执行,要么都不执行,已执行的部分回滚。
用回滚保证原子性:回滚日志(Undo Log)记录着事务所执行的修改操作,回滚时反向执行这些修改即可。
2、一致性(Consistency)
数据库从一个【正确有效】的状态转移到另一个【正确有效】的状态。
【正确有效】:满足数据库的预定约束。例如,A账户有90元,现在让他给B转100元,而银行的约束是账户余额不得小于0,那么这笔转账如果成功,就相当于有从正确状态转移到了错误状态,因为打破了约束。
通过原子性,持久性,隔离性最终实现数据的一致性。
3、隔离性(Isolation)
并发事务之间互不影响。 相互独立。
用【读写锁+MVCC】来保证隔离性
4、持久性(Durability)
一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。 即使系统崩溃,事务执行的结果也不能丢失。
用重做保证持久性:重做日志(Redo Log)记录的是数据页的物理修改。
Java是怎样实现事务的?
在方法上使用 @Transactional 注解。检测到该注解时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象 。
二、并发一致性问题
在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题。
丢失修改
A和B两事务先后修改一个数据,B的修改覆盖了A的修改
读脏数据
A修改一个数据还未提交,B 此时读取这个数据。随后 A 提交了修改,那么 B 读到的数据就是脏数据。
不可重复读
A 读取一个数据,然后B 对该数据做了修改。如果 A 再次读取这个数据,会发现读出的值发生了变化。
幻影读
A 读取某个范围的数据,然后B 在这个范围内插入数据,如果 A 再次读取这个范围的数据,会发现读出的数据量发生变化,出现了“虚幻”的新纪录。
产生并发一致性问题的主要原因是破坏了事务的隔离性,数据库自身提供了事务的隔离级别,更高级的,可以用锁来做并发控制。
三、四种隔离级别(从高到低)
-
串行化:事务间完全隔离,不能并发,只能串行执行。可避免脏读、不可重复读、幻读的发生。
-
可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。可避免脏读、不可重复读,但幻读仍有可能发生。(mysql默认)
-
读提交:事务只能看见已经提交的数据,也就是说,一个事务提交之后,它所做的修改才会被其他事务看到。可避免脏读的发生。(oracal默认)
-
读未提交:一个事务可以看到其他事务未提交的变更。最低级别,任何情况都无法保证。
隔离级别越高,并行性越低,数据库性能越低,当前事务处理的中间结果对其它事务不可见程度越高。
MySQL在可重复读隔离级别下不会出现幻读的现象,这是因为Innodb提供了 MVCC 和 间隙锁 来解决。
对于广义的数据库,可重复读隔离级别下是会出现幻读的。
四、谈谈你对MVCC 的了解?
MVCC叫做多版本并发控制。它为事务分配单向增长的时间戳,每次修改保存一个版本。读操作只读该事务开始前的数据库快照,并且可以读到多个版本的数据,从而解决了脏读、不可重复读和幻读问题。
注意:MVCC只能解决快照读下的幻读问题,而当前读下的幻读问题需要用间隙锁来解决。
快照读与当前读的区别:快照读,读的是历史版本,不用加锁。当前读,读取的是最新版本,需要加锁。