事务执行过程
- 事务开始
- 查询待更新数据至内存
- 将缓存数据放到Innodb缓存区
- 记录undo log对应的redo log至缓存区 --异步刷盘机制刷盘
- 记录undo log至缓存区
- 记录待更新数据对应的redo log至缓存区 --异步刷盘机制刷盘
- 更新缓存中的待更新数据
- commit触发redo log刷盘
- undo log和数据页按照刷盘机制刷盘
- 事务结束
redo log
redo log(重做日志):每当操作时,在磁盘数据变更之前,将操作写入redo log,这样当系统奔溃重启后可以继续执行。
- mysql buffer pool写入是随机IO,redo log是顺序IO,更高效
- redo日志占用的空间非常小
redo log是基于磁盘数据结构记录的日志,用于在db崩溃时恢复由未完成的事务写入的正确数据。在正常操作期间,redo log会对SQL语句或低级API调用产生的更改表数据的请求进行编码。在意外关闭之前没有完成数据文件更新的修改将在初始化期间和连接被接受之前自动重放。关于redo log在崩溃恢复中的作用,请参见“InnoDB恢复”章节14.19.2 Section 14.19.2, “InnoDB Recovery”
默认redo log在磁盘上由两个文件表示 ib_logfile0 and ib_logfile1。MySQL以循环的方式写redo log文件。redo log中的数据根据受影响的记录进行编码;此数据统称为重做数据。通过redo log的数据通道由不断增加的LSN(log sequence number)值表示。
undo log
InnoDB引擎中的回滚通过undo log来实现,当需要修改某个数据时候,首先把数据页从磁盘加载到Buffer Pool中,然后记录一条undo log日志,之后再进行修改。
undo log撤消日志是与单个读写事务关联的撤消日志记录的集合,一个undo log包含怎样撤销一个事务对聚簇索引(主键索引)记录最近的变更信息。如果另一个事务需要一致性读操作查看原始数据,从undo log记录中检索未发生变更的数据返回(事务未提交前)。Undo日志存在于Undo日志段中,Undo日志段包含在回滚段中。回滚段位于system表空间、undo表空间和临时表空间中。
位于临时表空间中的Undo logs用于用户定义的临时表数据变更的事务。这些undo logs不需要redo-logged,因为他们不需要崩溃恢复。他们仅用于服务运行时的rollback。这类undo log避免了redo日志I/O提高了性能。
InnoDB支持最大128的回滚段,其中32个被分配给临时表空间。这样就剩下96个回滚段可以分配给修改常规表中的数据的事务。The innodb_rollback_segments
参数定义InnoDB使用回滚段的数量。
一个回滚段支持的事务数依赖于回滚段中undo slot的数量与每个事务需要的undo log数量。
根据InnoDB页面的大小,回滚段中的undo槽的数量是不同的。
InnoDB Page Size | Number of Undo Slots in a Rollback Segment (InnoDB Page Size / 16) |
---|---|
4096 (4KB) | 256 |
8192 (8KB) | 512 |
16384 (16KB) | 1024 |
32768 (32KB) | 2048 |
65536 (64KB) | 4096 |
一个事务被分配的最大4个undo log,每一个对应以下操作类型:
Undo log被按需分配。例如,一个事务执行 INSERT
, UPDATE
, and DELETE
操作常规表与临时表需要4个undo log全部分配。一个事务仅执行 INSERT
操作常规表需要一个undo log。常规表执行操作的事务由已分配的系统表空间或undo表空间的回滚段分配undo log。临时表执行操作的事务由已分配的临时表空间的回滚段分配undo log。
分配给事务的undo log在事务期间保持与事务绑定。例如,分配给常规表 INSERT
操作事务的undo log,用于该事务常规表上执行的所有 INSERT
操作。
考虑到上述因素,可以使用以下公式来估算InnoDB能够支持的并发读写事务的数量。
(innodb_page_size / 16) * (innodb_rollback_segments - 32)
(innodb_page_size / 16 / 2) * (innodb_rollback_segments - 32)
- 如果在临时表上操作的每个事务执行一个
INSERT
,InnoDB
能够支持的并发读写事务数量为:
(innodb_page_size / 16) * 32
(innodb_page_size / 16 / 2) * 32
redo log 与 undo log的关系
undo log日志的完整性和可靠性需要redo log日志来保证,所以数据库崩溃需要先做redo log数据恢复,然后做undo log回滚。
事务执行COMMIT操作时,会将本事务相关的所有redo log进行落盘,落盘成功才算COMMIT成功。然后内存中的undo log和脏页按照同样的规则进行落盘。如果此时发生崩溃,则只使用redo log恢复数据。
Committing log
阿里的OceanBase使用的Commit Logging来实现事务
Commit Logging只有在日志记录全部都安全写入磁盘之后,数据库在日志中看到代表事务成功的Commit Record之后,才会根据日志上的信息对真正的数据进行修改,修改完成后,再在日志中加入一条End Record表示事务已完全持久化。
与WAL的区别是:WAL允许在事务提交之前,提前写入变动数据,而Commit Logging不行;因此WAL中有undo log,Commit Logging不需要undo log
Doublewrite Buffer
官方定义:buffer pool数据刷盘前数据存放的地方(即刷盘前多一个中间层即DWB)
The doublewrite buffer is a storage area where InnoDB writes pages flushed from the buffer pool before writing the pages to their proper positions in the InnoDB data files.
为什么会有Doublewrite Buffer(之后简称DWB)?
因为数据库系统的page大小,与磁盘系统的page大小不一致。
查看操作系统最小操作单位 page大小,常见为4k
$ getconf PAGESIZE
4096
查看mysql innodb引擎最小操作单位 page大小,常见为16k
mysql> show variables like '%innodb_page_size%';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| innodb_page_size | 16384 |
+------------------+-------+
1 row in set (0.01 sec)
那么如果16k向4k的页面写数据,写到一半系统重启了怎么半?数据库的页面数据不完整了,也就导致了数据无法复原,数据丢失。那么DWB作用就来了,DWB缓存会双写磁盘互相作为彼此的备份,一份Doublewrite File(DWF)(顺序写),一份数据文件(随即写)。
双写的性能影响呢?
性能必然有影响,但是影响不大,原因如下:
1. buffer pool中的页数据属于内存复制到DWB的内存,速度很快
2. DWB的内存fsync刷到DWF的磁盘,属于顺序追加写,速度也很快
3. DWB本身很小,再加上其可以切块后分批执行,每次操作的数据更小,比如:1Mb,性能很好
事务四大特性
ACID
atomicity, consistency, isolation, and durability首字母简写。这些属性在数据库系统中都是理想的,并且都与事务的概念密切相关。InnoDB的事务特性遵循了ACID原则。
A(undo log):事务是可提交或回滚的原子工作单元。当一个事务产生多个DB变更,要么所有变更全部成功在事务提交时,要么所有变更全部撤销在事务回滚时。
C(WAL(Write-ahend logging,预写式日志)/Commit Logging):在事务每次提交或回滚后以及事务进行时,数据库始终保持一致性状态。如果相关数据变更横跨多张表,查询看到的要么全部是旧值,要么全部是新值,不会是混合的新旧值
I(读写锁+MVCC):所有进行中的事务彼此之间是相互保护(隔离的);他们不能相关干涉或者看到彼此未提交的数据。这种隔离是通过锁定机制实现的。有经验的用户可以调整隔离级别,在确保事务确实不会相互干扰的情况下,减少保护以提高性能和并发性。
D(redo log):事务的结果是持久性的;一旦提交操作成功,由事务产生的变更不会发生电源故障,系统崩溃,竞态条件,或许多非数据库应用程序容易受到的其他潜在危险。持久性通常涉及到对磁盘存储的写入,并具有一定的冗余,以防止在写操作期间断电或软件崩溃。(在InnoDB中,doublewrite缓冲区有助于提高持久性。)
总结
redo log与binlog区别
- redo log基于磁盘物理数据结构存储,binlog为逻辑日志结构(即使row模式也是记录行变更的数据值)
- redo log用于数据持久化(处理缓存与磁盘一致性问题,可复用),binlog用于主从复制(处理磁盘与磁盘一致性问题,不可复用,append only)
- redo log可以合并提交以降低IO成本(Group Commit for Redo Log Flushing)
- redo log是面向innoDB引擎,binlog面向DB(包含所有引擎表均会记录binlog日志)
mysql事务实现原理
原子性:基于undo log实现,undo log可以根据LSN进行撤销操作。要么全部成功,要么全部撤销
一致性:个人理解遵循了AID原则的数据库一定是具备一致性的
隔离性:基于数据库排它锁,共享锁机制实现,MVCC(undo log+read view)多版本并发控制机制可以实现无锁情况下避免脏读/不可重复读/幻读,但是不能解决更新丢失。
持久性:基于redo log + undo log实现,缓存区对应即:redo log buffer,undo log buffer
注:DIO模式不经过linux内核缓存
参考
https://dev.mysql.com/doc/refman/5.7/en/innodb-redo-log.html
https://dev.mysql.com/doc/refman/5.7/en/innodb-undo-logs.html
https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_consistent_read
https://dev.mysql.com/doc/refman/8.0/en/innodb-doublewrite-buffer.html