由事务扩展开谈一谈

事务的四个特性

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 写入磁盘,最后事务提交。

image

在 BC 两个点会出现数据不一致的问题。服务器重启后发现 redo log 中处于 prepare 状态的记录,再根据事务ID 检查 binlog 是否包含此条 redo log 的更新内容,如果不包含,redo log 丢弃此次变更,如果包含,事务会提交。

undo log

属于 InnoDB 的事务日志,属于逻辑日志,其回滚作用,是保障事务原子性的关键。

记录的是数据修改前的状态。

比如事务执行一条更新语句时,会先在 undo log 中写入一条相反操作的逻辑日志。

同一个事务中对同一条记录的修改不会记录多条日志,undo log 只保存了数据的原始版本。

MVCC

MVCC,即多版本并发控制,在 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 会用链表有序存储一条记录的所有历史版本,链首是最新版本,链尾是最旧版本。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyggyf.html