write(刷盘):MySQL从buffer pool中将内容写到系统的page cache中,并没有持久化到系统磁盘上。这个速度其实是很快的。 fsync(持久化到磁盘):从系统的cache中将数据持久化到系统磁盘上。这个速度可以比较慢,而且也是IOPS升高的真正原因。
一、事务及其特性
一般当我们的功能函数中有批量的增删改时,我们会添加一个事务包裹这一系列的操作,要么这一组操作全部执行成功,只要有一条SQL执行失败了我们就全部回滚。这么做其实是保护我们的数据库中不出现脏数据,整体数据会变的可控。
对MySQL来说你可以通过下面的命令显示的开启、提交、回滚事务:
# 开启事务begin;# 或者下面这条命令start transaction;# 提交commit;# 回滚rollback
; 但是日常开发中大家普遍使用编程语言操作数据库。比如Java、Golang… 在使用这种具体编程语言持久层的框架时,它们一般都支持事务操作,比如:在Spring中你可以对一个方法添加注解@Transctional显示的开启事务。Golang的beego中也提供了让你可以显示的开启事务的函数。
有一点不好的是:大家在享受这种编程框架带来的便利的同时,它也屏蔽了对MySQL事务认知。
查看binlog:mysqlbinlog --base64-output=DECODE-ROWS -v binlog.000011
执行增删改语句的时候并没有显示添加begin、commit命令,但MySQL实际执行时,自动添加上了!
原因很简单,参数 autocommit = on。一般线上库都会将这个参数置为ON,Mysql会自动的开启一个事务,并且会自动的帮你提交。也就是说: 当这个参数为ON时,你使用的DAO持久层框架发送给数据库的SQL其实都会被放在一个事务中执行,然后这个事务被自动提交,而我们对这个过程是无感知的。
具体一点,比如你使用某框架的@Transctional注解。然后你所有的操作都放在这个事务中执行。这时你使用的持久层框架肯定会向MySQL发送一条命令:begin;或者是start transcation;来保证你这一组SQL中执行一条SQL后,开启的事务不会被MySQL自动帮你提交了。
但是关闭autocommit之后,MySQL不会帮你自动提交事务,全靠研发同学自己来维护,就容易会出现长事务,在内存中产生一个极其长的undo log链条。
二、简单看下两阶段提交(2PC)的流程
其实所谓的两阶段就是把一个事务分成两个阶段来提交。
MySQL想要准备事务的时候会先写redolog、binlog分成两个阶段。
两阶段提交的第一阶段 (prepare阶段):redolog写入log buffer,并fsync持久化到磁盘,在redolog事务中记录2PC的XID,在redolog事务打上prepare标识。
两阶段提交的第二阶段(commit阶段):binlog写入log buffer,并fsync持久化到磁盘,在binlog事务中记录2PC的XID,同时在redolog事务打上commit标识。
三、两阶段写日志用意
binlog默认都是不开启的状态!也就是说,如果根本不需要binlog带给你的特性(比如数据备份恢复、搭建MySQL主从集群),那也就不需要写binlog,也用不到两阶段提交。只用一个redolog就够了。无论你的数据库如何crash,redolog中记录的内容总能让你MySQL内存中的数据恢复成crash之前的状态。
所以说,两阶段提交的主要用意是:为了保证redolog和binlog数据的安全一致性。只有在这两个日志文件逻辑上高度一致了。你才能放心的使用redolog帮你将数据库中的状态恢复成crash之前的状态,使用binlog实现数据备份、恢复、以及主从复制。而两阶段提交的机制可以保证这两个日志文件的逻辑是高度一致的。没有错误、没有冲突。
当然,两阶段提交能做到足够的安全还需要你合理的设置redolog和binlog的fsync的时机。
四、如何判断binlog和redolog是否达成了一致
两份日志文件逻辑对齐的标记是有一份相同的XID。就是这种两阶段的机制保证了两个日志(在分布式事务中就是多个数据节点)在逻辑上能达到一致的效果。
当MySQL写完redolog并将它标记为prepare状态时,并且会在redolog中记录一个XID,它全局唯一的标识着这个事务。而当你设置sync_binlog=1时,做完了上面第一阶段写redolog后,mysql就会对应binlog并且会直接将其刷新到磁盘中。
上面第一节中的图就是磁盘上的row格式的binlog记录。binlog结束的位置上也有一个XID。只要这个XID和redolog中记录的XID是一致的,MySQL就会认为binlog和redolog逻辑上一致。就上面的场景来说就会commit,而如果仅仅是rodolog中记录了XID,binlog中没有,MySQL就会RollBack。
那当重启MySQL后,update对BufferPool中做出的修改是会被回滚还是会被提交呢?
答:会根据redolog将修改后的recovery出来,然后提交。
那为什么会这样做呢?
答:其实总的来说,不论mysql什么时刻crash,最终是commit还是rollback完全取决于MySQL能不能判断出binlog和redolog在逻辑上是否达成了一致。只要逻辑上达成了一致就可以commit,否则只能rollback。比如binlog已经写了,但是MySQL最终选择了回滚。那代表你的binlog比BufferPool(或者Disk)中的真实数据多出一条更新,日后你用这份binlog做数据恢复,是不是结果一定是错误的?
五、两阶段提交设计的初衷 - 分布式事务
其实两阶段提交更多的被使用在分布式事务的场景。
MySQL单机本来是支持事务的,但是这里所谓的分布式事务实际上指的是跨数据库、跨集群的事务。比如说你公司的业务太火爆了,每天都产生大量的数据,这些数据不仅单表存不下,甚至单库都存不下了(已经达到了服务器硬件存储的瓶颈)。
那你怎么办?是不是只能将单库拆分成多库?
拆分成多库就会面临这样一个新的问题。假设Tom给Jerry转账,但是由于你拆分了数据库,原本在同库同表上的Tom和Jerry的信息,被拆分进A库a表和B库b表。那再发起转账逻辑时,万一失败了。如何回滚保证数据的安全?这就是分布式事务的要解决的问题。
通常各大公司都有自己的支持分布式事务中间件,中间件的作用本质上就是处理好各个数据库节点之间两阶段提交的问题。
简单来说:就是中间件要协调各个数据节点。
- 第一阶段:中间件告诉各数据库节点,让它们开启XA事务,然后判断所有数据库节点是否已经处于prepare状态;
- 第二阶段:中间件判断事务提交还是回滚的阶段。如果所有节点都prepare那就统一提交。但凡出现一个失败的节点,统一回滚。
