四大特性(ACID)
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
原子性
一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
简单说就是:一个事务要么完成要么失败
原理
InnoDB存储引擎提供了undo log(回滚日志),在undo log中保存了和执行操作相反的记录,如果事务执行失败则会执行rollback,这时就需要使用到undo log中的日志记录。例:写入2个insert,那就会记录下对应的2个delete。
持久性
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
redo log
数据存放在磁盘,每次都需要从磁盘读取数据,效率很低,所以innoDB提供了缓存,每次写入数据都是先写入缓存中,缓存中的数据会定时刷新磁盘。
这样就带来了一个问题,数据写入缓存后,服务器宕机了怎么办,数据是不是就丢失了? 要真数据丢失了,就没人用mysql了吧,每次写入时先写入redo log再写入缓存。就算宕机了,也可以从redo log中恢复数据,再更新缓存,保证了数据的持久性。
一致性
在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
隔离性
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
简单说: 隔离性追求的是并发情形下事务之间互不干扰。
隔离性的四大级别
隔离级别越低,系统开销越低,可支持的并发越高,但隔离性也越差。
- 读未提交(Read uncommitted)
- 读已提交(read committed)
- 可重复读(repeatable read)
- 串行化(Serializable)
理解四大隔离级别先要理解 脏读、不可重复读、幻读
- 脏读:当前事务(A)中可以读到其他事务(B)未提交的数据。
| 时间 | 事务A | 事务B |
|---|---|---|
| T1 | 开始 | 开始 |
| T2 | 查询张三的数学分数为59 | |
| T3 | 修改张三的数学分数为60 | |
| T4 | 查询张三的数学分数为60 | |
| T5 | 提交事务 |
我们可以清楚的看见事务B在T5时刻在提交,事务A却在T4时已经读到事务B提交的内容了。
- 不可重复读:在事务A中先后两次读取同一个数据,两次读取的结果不一样。
| 时间 | 事务A | 事务B |
|---|---|---|
| T1 | 开始 | 开始 |
| T2 | 查询张三的数学分数为59 | |
| T3 | 修改张三的数学分数为60 | |
| T4 | 提交事务 | |
| T5 | 读取张三的数学分数为60 | |
| 可以清楚看见,事务A在两次读取张三的数学分数,取得的结果是不同的。 |
不可重复读与脏读的区别前者为提交后读取的数据不同,后者为未提交读取的数据不同。
- 幻读:在事务A中按照某个条件先后两次查询数据库,两次查询结果的条数不同。
| 时间 | 事务A | 事务B |
|---|---|---|
| T1 | 开始 | 开始 |
| T2 | 查询张三小于60分的科目:数学 | |
| T3 | 插入张三英语成绩,分数为59 | |
| T4 | 提交事务 | |
| T5 | 查询张三小于60分的科目:数学、英语 | |
| 在前后两次查询中,取到的数据条数是不同。 |
幻读与不可重复读前者是查询出的记录条数不同,后者则是查询单条记录不同。
不可重复读与幻读的区别可以通俗的理解为:前者是数据变了,后者是数据的行数变了。
各个隔离级别存在的问题
| 隔离级别级别 | 问题 |
|---|---|
| 读未提交 | 脏读、不可重复读和幻读 |
| 读已提交 | 不可重复读和幻读 |
| 可重复读 | 幻读 |
| 串行化 | 没问题 |
在实际应用中,读未提交在并发时会导致很多问题,而性能相对于其他隔离级别提高却很有限,因此使用较少。可串行化强制事务串行,并发效率很低,只有当对数据一致性要求极高且可以接受没有并发时使用,因此使用也较少。因此在大多数数据库系统中,默认的隔离级别是读已提交或可重复读Mysql默认为可重复读。
MVCC
可重复读解决脏读、不可重复读、幻读等问题,使用的是MVCC:MVCC全称Multi-Version Concurrency Control,即多版本的并发控制协议。
MVCC最大的优点是读不加锁,因此读写不冲突,并发性能好。InnoDB实现MVCC,多个版本的数据可以共存,主要基于以下技术及数据结构:
-
隐藏列:InnoDB中每行数据都有隐藏列,隐藏列中包含了本行数据的事务id、指向undo log的指针等。
-
基于undo log的版本链:前面说到每行数据的隐藏列中包含了指向undo log的指针,而每条undo log也会指向更早版本的undo log,从而形成一条版本链。
-
ReadView:通过隐藏列和版本链,MySQL可以将数据恢复到指定版本;但是具体要恢复到哪个版本,则需要根据ReadView来确定。所谓ReadView,是指事务在某一时刻给整个事务系统(trx_sys)打快照,之后再进行读操作时,会将读取到的数据中的事务id与trx_sys快照比较,从而判断数据对该ReadView是否可见,即对事务A是否可见。
trx_sys中的主要内容,以及判断可见性的方法如下:
- low_limit_id:表示生成ReadView时系统中应该分配给下一个事务的id。如果数据的事务id大于等于low_limit_id,说明该事务还没执行,则对该ReadView不可见。
- up_limit_id:表示生成ReadView时当前系统中活跃的读写事务中最小的事务id。如果数据的事务id小于up_limit_id,说明该事务已经执行完成,则对该ReadView可见。
- rw_trx_ids:表示生成ReadView时当前系统中活跃的读写事务的事务id列表。如果数据的事务id在low_limit_id和up_limit_id之间,则需要判断事务id是否在rw_trx_ids中:如果在,说明生成ReadView时事务仍在活跃中,因此数据对ReadView不可见;如果不在,说明生成ReadView时事务已经提交了,因此数据对ReadView可见。如下:
读取数据时会生成ReadView之后再进行读操作时,会将读取到的数据中的事务id与trx_sys快照比较,从而判断数据对该ReadView是否可见,即对事务A是否可见。