事务的四个特性
ACID
原子性:事务中的操作要么全部成功,要么全部失败。通过 undo log 实现
一致性:数据库在事务执行前后都处于一个正确的状态。
隔离性:事务执行过程中,不应该收到其他事务的打扰,并发的事务要隔离。通过锁、MVCC实现
持久性:事务执行完成之后,数据将永远保存在数据库中,即使出现意外宕机的情况,也不应该对这部分数据造成任何影响。通过 redo log 实现
事务的四个隔离级别读取未提交:事务的修改,即使没有提交,对其他事务也都是可见的。这种现象叫脏读。
读取已提交:事务读取已提交的数据,大多数数据库的默认隔离级别。当一个事务在执行过程中,前后读取的信息不一样,这种情况称为不可重复读。
可重复读:mysql 的默认隔离级别。它解决了脏读、不可重复读问题、存在幻读问题。幻读:当一个事务A读取某一个范围的数据时,另一个事务B在这个范围插入行,A事务再次读取这个范围的数据时,会产生幻读。
可串行化:所有事务依次逐个执行,互相之间没有干扰。
redo log & bin log数据是存放在磁盘中的,如果每次读写数据都需做磁盘IO操作,并发场景下性能就会很差。为此 Mysql 引入缓存 Buffer Pool 来缓解数据库的磁盘压力。
当从数据库读数据时,首先从缓存中读取,如果缓存中没有,则从磁盘读取后放入缓存;当向数据库写入数据时,先向缓存写入,此时缓存中的数据页数据变更,这个数据页称为脏页;Buffer Pool 中的数据会定期刷到磁盘中,这个过程称为刷脏页。
Mysql 宕机,如果刷脏页还未完成,更新就会丢失,无法保证事务的持久化。
为了解决这个问题引入了 redo log
redo log 是属于 InnoDB 的事务日志,它是物理日志,记录的是数据库中每个页的修改,而不是某一行或某几行修改成怎样,可以用来恢复提交后的物理数据页,且只能恢复到最后一次提交的位置。
redo log 使用了 write ahead logging 技术,即先写日志,再修改 Buffer Pool 中的数据,至于缓存何时刷盘则有后台线程异步处理了。如果写的顺序反过来,可能存在日志和数据不一致的情况。
说了 redo log 就不得不说 bin log
bin log 是 Mysql Server 层面的,记录的所有的 DDL、DML 操作,用来备份数据和主备同步数据。想要做到主备数据一致就得保证 redo log 和 binlog 的一致性,因此 redo log 的写入使用两阶段提交,如下图,prepare阶段 redo log 写入磁盘,然后 binlog 写入磁盘,最后事务提交。
在 BC 两个点会出现数据不一致的问题。服务器重启后发现 redo log 中处于 prepare 状态的记录,再根据事务ID 检查 binlog 是否包含此条 redo log 的更新内容,如果不包含,redo log 丢弃此次变更,如果包含,事务会提交。
undo log属于 InnoDB 的事务日志,属于逻辑日志,其回滚作用,是保障事务原子性的关键。
记录的是数据修改前的状态。
比如事务执行一条更新语句时,会先在 undo log 中写入一条相反操作的逻辑日志。
同一个事务中对同一条记录的修改不会记录多条日志,undo log 只保存了数据的原始版本。
MVCCMVCC,即多版本并发控制,在 InnoDB 中的实现主要是为了提高数据库并发性能,用更好的方式去处理读写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。
当前读与快照读:
当前读:select for update、update、insert、delete 这些操作都是当前读,什么叫当前读?读取的是记录的最新版本,且会对读取的记录加锁,保证其他并发事务不能修改当前记录
快照读:快照读是 Mysql 为 MVCC 实现的一个非阻塞读并发功能,读取的不一定是最新记录,也有可能是某个历史记录。
MVCC解决的问题
数据库并发场景有三种:
读读:不存在任何问题,也不需要并发控制
读写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读、幻读、不可重复读
写写:有线程安全问题,可能存在更新丢失问题
MVCC 解决的问题
在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
解决脏读、幻读、不可重复读等事务隔离问题,但是不能解决更新丢失问题(加锁解决)
MVCC的实现原理
MVCC 由三个隐式字段、undo log、read view 三个组件实现。
三个隐式字段:每行记录除了我们自定义的字段外,还有数据库定义的一些隐式字段字段
DB_TRX_ID:最近修改事务ID,记录创建这条记录或者最后一次修改该记录的事务ID
DB_ROLL_PTR:回滚指针,指向 undolog 中上一个数据版本
DB_ROW_JD:隐藏的主键,如果数据表没有主键,那么 innodb 会自动生成一个6字节的row_id
undo log:
undolog 会用链表有序存储一条记录的所有历史版本,链首是最新版本,链尾是最旧版本。