最近重温了下Mysql相关原理的书,主要是事务的实现,这个对设计一个稳定的系统很有借鉴意义,发现事务的实现还是蛮复杂的,把中间看到的一些知识点和想法整理下。
一、事务的核心属性
A:原子性
事务要么不执行,要么全部执行完,不会执行其中一部分;
B:一致性
主要是一些约束,如主键,外键,等
I:隔离性
指多线程之间的修改不会影响其它线程,如线程A改了数据没提交前B线程可以不看到,这个和事务隔离级别配置相关;
D:持久性
事务一旦提交了就不能丢失;
其中B主要是一些规则比较好理解,D也比较容易,事务提交的时候将数据全部刷新到存储介质上就行;A和I设计到多线程并发问题,还有异常处理问题,和D有些相关,如何保证提交的时候全部提交成功,而不会提交一部分。
二、Write-Ahead
数据库是IO型应用,对记录的修改最终是要写到磁盘上,一次事务可能修改多条记录,而这些记录会分布在磁盘的不同位置上,而磁盘是顺序写入性能高,下面是磁盘物理结构,具体组成就不介绍了;
如果大量随机磁道写入则会造成性能低下,所以有了Write-Ahead。
具体过程如下,数据库把对所有磁盘的修改以日志追加的方式写入,然后异步将这些内容刷新到真正存放数据的位置,这样就保证用户请求的时候性能是最高的;
举个例子,一个经典的例子是转账,需要将一个账上加钱,而另一个账上减钱,这个事务至少要执行2条Sql语句:
update User set balance=balance-100 where id=100;
update User set balance=balance+100 where id=200;
假设第一条记录在0号盘面上,第二条记录在1号盘面上,这个时候数据库记录2条日志,大概内容如下:
第1条日志:修改0号盘面X1磁道数据为Y1;
第2条日志,修改1号盘面X2磁道数据库Y2;
只要日志写入成功然后就可以返回给用户成功了,会有后台线程读取日志里的数据真正把0号盘面和1号盘面上的数据写回去。
可以看到,事务提交分成2部分,写日志和写数据,写日志记录数据改了哪些地方,这个和硬件相关,这部分是顺序写的;而写数据的部分是离散的,但这部分是后台写,所以性能慢一点没关系,要保证数据的正确性。
三、Redo Log
上面说了事务提交前后要写2部分数据,一是日志,二是数据,其中日志在Mysql准确的说是InnoDB中就是以Redo Log来表示,这里讲几个细节:
1、Redo Log以Redo Log Block来管理日志,每个Block是512字节,为什么是512,因为早期磁盘一个扇区就是512,这样可以保证写入的原子性,即不会512字节只会写一部分成功
2、日志文件不能无限扩张
日志过了一段时间就不需要了,这个时间是指数据的部分写入完成,因为这部分是异步写入的,如果中间当机则需要通过日志部分来恢复。
3、LSN(Log Sequence Number)
Redo Log日志编号,记录的是数据库从安装启动开始到当前写入的总的日志总量。这个用于恢复中,先不讲太细的东西。
四、ARIES恢复算法
这个是20世纪90年代IBM几位研究员提出的一个算法集,主要论文如下:
AREIS: A Transaction Recovery Method Supporting Fine-Granularity Locking and Parial Rollbacks Using WriteAhead Logging
这个算法影响深远,基本上现在的关系型数据库都吸收了这个思想。
先大概讲下基本原理,Mysql InnoDB中是以页为最小单位来管理磁盘的,一般为16KB,如果一个事务修改了某个页会将这个页标记为脏页,然后异步刷新到磁盘上。
ARIES算法大概分3个阶段:
1、分析阶段
确定哪些数据页是脏页,为阶段2的Redo做准备。
2、确定哪些事务未提交
未提交的事务也写入Redo Log,如何判断哪些未提交呢,这里用到了Checkpoint机制,它是每隔一段时间将内存中的所有数据刷新到磁盘,注意是所有,对于数据库的场景来说,现在几百GB大小很常见了,这样做肯定不现实,一个是量太大,二是刷新过程中还要停止所有用户请求,像JAVA垃圾回收一样,要Stop The World。
所以一般采用Fuzzy CheckPoint,具体是在内在中维护二张表:活跃事务表和脏页表。
活跃事务表:维护一个关键变量LastLSN,即该事务产生的日志最后一条日志的LSN。
脏页表是当前所有未刷新到磁盘上的页的集合,系统为每个页记录了RecoveryLSN,即导致该页面为脏页最早的LSN。
Fuzzy CheckPoint就是对这2个关键表做一个Checkpoint,这样数据量就比较小了,形成一条日志写入Redo Log。
有了这2张表就可以取出系统意外宕机的时候未提交的事务了,具体过程如下,从最近一次CheckPoint开始扫描Redo Log,遇到一个事务则加入到集合,如果遇到事务提交的日志则将事务从集合中删除掉。
另外就是求宕机的时候未刷盘的脏页集合,从最后一个CheckPoint开始一直扫描,一直到Redo Log的结束,如果日志中记录的是新的页面就加入到脏页集合,当然这过程可能在误判,不过没关系,真正把Redo Log写入到数据存放真正位置是幂等的。在刷磁盘的时候,磁盘上每个页面会记录最后一次刷新的LSN,刷新过程中会判断两者的大小,如果页面的LSN比Redo Log的大则跳过这条日志。