事务需要满足四个特性,就是ACID。
- 原子性(Atomicity,或称不可分割性)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
开始
在很久很久之前,没有数据库,我创建了多个txt文件,每个txt文件存储一些数据,我写了一个工具类,进行CRUD操作。这个简单的数据库天然支持持久性(Durability)。
原子性的实现
有一个操作需要修改两个txt文件,我希望同时成功或者失败,于是新创建了个文件,叫做undo log,中文名叫回滚日志,用于记录数据被修改前的信息,每操作一个文件的一次我就记录一下。undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。我实现了原子性(Atomicity)的需求。
持久性的实现
由于频繁操作txt文件,每个txt文件所在的磁盘位置不一致,磁头不停的转,每次读写数据都需要磁盘IO,效率会很低。为此,我(InnoDB引擎)提供了缓存(Buffer Pool),Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。Buffer Pool的使用大大提高了读写数据的效率,但是也带了新的问题:如果MySQL宕机,而此时Buffer Pool中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。
于是,redo log被引入来解决这个问题:当数据修改时,除了修改Buffer Pool中的数据,还会在redo log记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是WAL(Write-ahead logging,预写式日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。
既然redo log也需要在事务提交时将日志写入磁盘,为什么它比直接将Buffer Pool中修改的数据写入磁盘(即刷脏)要快呢?主要有以下两方面的原因:
(1)刷脏是随机IO,因为每次修改的数据位置随机,但写redo log是追加操作,属于顺序IO。
(2)刷脏是以数据页(Page)为单位的,MySQL默认页大小是16KB,一个Page上一个小修改都要整页写入;而redo log中只包含真正需要写入的部分,无效IO大大减少。
隔离性的实现
假设事务不满足隔离性,我们耳熟能详的转钱例子就会出问题。所以事务需要满足隔离性。隔离性与原子性、持久性侧重于研究事务本身不同,隔离性研究的是不同事务之间的相互影响。隔离性是指,事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。严格的隔离性,对应了事务隔离级别中的Serializable (可串行化),但实际应用中出于性能方面的考虑很少会使用可串行化。
隔离性追求的是并发情形下事务之间互不干扰。简单起见,我们仅考虑最简单的读操作和写操作(暂时不考虑带锁读for update等特殊操作)。如果没有隔离性的需求,就不会有MVCC的存在。锁的实现也会很简单,没有读锁一类的,只有一个写锁防止并发导致的错误就行了。
MySQL隔离级别有以下四种(级别由低到高):
- READUNCOMMITED(未提交读)
- READCOMMITED(提交读)
- REPEATABLEREAD(可重复读)
- SERIALIZABLE (串行化)
1. READUNCOMMITED未提交读
如何实现?
如果mysql 什么也不做,默认就是这个这样。
有什么好处?
快,效率高
有什么坏处?
有脏读,不可重复读,幻读的问题。脏读在实际应用中就是别人给你转钱,还没扣掉别人的,你查看账户的钱已经多了。
2. READCOMMITED提交读
如何实现?
mysql引入了MVCC的概念
有什么好处?
解决了脏读,读取到的数据是已经提交的数据。
有什么坏处?
效率变慢,依旧有不可重复读,幻读的问题。不可重复读就是你查询出来的数据第一次看到的是V1,再次查询时最新的值V2。这是对的,读到最新的值没有任何问题,但是在核查对账,主从备份的场景下,一个事务写A表和B表(两个表有业务关系),当我们在事务写完A表,没有写完B表的时候读取A表和B表数据,我们就会导致对账失败。
3.REPEATABLEREAD(可重复读)
如何实现?
mysql引入了MVCC的概念
有什么好处?
解决了不可重复读,一次事务中多次读取到的数据是相同的。
有什么坏处?
效率变慢,依旧有幻读的问题。解决了不可重复读的问题,但是没有解决幻读,不可重复读主要是删除和更新的影响,我们能够屏蔽掉,但是我们无法屏幕掉insert的影响,insert的影响就是幻读。在实际应用中就是你在一个方法中分页查询数据,后插入的数据如果在前面,会导致页码乱掉,你用这个数据打印还是做其他作用都不是一个合法的数据。
4.SERIALIZABLE (串行化)
如何实现?
mysql引入了读写锁的概念。对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
有什么好处?
解决了幻读。
有什么坏处?
效率变慢
一致性的实现
一致性是事务追求的最终目标:前面提到的原子性、持久性和隔离性,都是为了保证数据库状态的一致性。此外,除了数据库层面的保障,一致性的实现也需要应用层面进行保障。
实现一致性的措施包括:
- 保证原子性、持久性和隔离性,如果这些特性无法保证,事务的一致性也无法保证
- 数据库本身提供保障,例如不允许向整形列插入字符串值、字符串长度不能超过列的限制等
- 应用层面进行保障,例如如果转账操作只扣除转账者的余额,而没有增加接收者的余额,无论数据库实现的多么完美,也无法保证状态的一致
其实幻读和不可重复读的区别是更新和新增
https://www.toutiao.com/a6777338939360412171/
未完待续