mysql 为了保证crash-safe, 是通过引入binlog(server 层的逻辑日志), redo log(innodb 存储引擎层日志), undo log(innodb 存储引擎层日志)来保证的。
binlog与存储引擎无关,每次对binlog日志的新增都是append, binlog可用于mysql的replica, 也可用于我们将MySQL的数据恢复到之前的某一个时刻。redo log是innodb存储引擎层的物理操作记录,记录把某个数据页中地址对应的数据修改什么值,为了能够回滚所有的操作,每条redo log会伴随一条undo log。 redo log的存储是一个环形的,不是无限append的。
为了保证crash-safe, 数据的提交过程是两阶段提交。
如果写binlog之前crash。MySQL恢复后,检查redo log和binlog不完备,此时根据undo log执行回滚,数据一致。
如果将redo log状态改为commit之前crash。MySQL恢复后,检查redo log和binlog,此时如果binlog不完整,则根据undo log回滚,数据一致;如果binlog完整,将redo log状态置为commit,数据一致。
首先我们看下主备一致
主库A有一个专门的线程dump_thread, 专门服务于从库B。从库B通过change master命令连上主库,通过执行start slave命令,启动两个线程io_thread, sql_thread, 分别用于与主库建立连接和读取主库的binlog并应用到自己的db数据里。
上图是一个M-S的主从架构,实际生产中是互为主备的。 如果Master挂了,Slave就变成主,对外提供写操作。互为主备的时候,我们需要考虑如何避免数据循环复制。一个解决方案就是生成binlog的时候,需要记录server对应的server id, 每个server在启动的时候分配一个唯一的server id。这样如果接收binlog的服务发现binlog的server id和自己server id一样,就会忽略掉。
然后我们再看下主备切换。
当一些异常情况主库不可用了,我需要将备库升级为主,但是需要考虑主备延迟的情况,如何操作,有两种思路:
可靠性优先原则
可用性优先原则
如果强行把步骤 4、5 调整到最开始执行,也就是说不等主备数据同步,直接把连接切到备库 B,并且让备库 B 可以读写,那么系统几乎就没有不可用时间了。这么做的代价是可能会出现数据不一致。
在生产环境中,一般会让MySQL主Server提供线上实时业务的读写服务,备Server提供只读服务以减轻对主Server的压力。一般情况下,我们默认能够容忍从备库读到的数据较主库可能有一定的延迟,但在某些场景下,我们需要备库的数据严格与主库一致,应该如何操作呢?
备选的方案有:(1)从主库读数据;(2)延迟n秒读备库;(3)半同步+seconds_behind_master来判断主从是否已同步。
MySQL通过写redo log避免直接写磁盘,大大提高了写入速度,这个技术就是Write-Ahead Logging。在InnoDB觉得时机“适当”时,才会把redo log的内容更新到磁盘。有4个时机: 1) 系统空闲时 2) redo log快被写满时 3) 内存快被写满时 4)Mysql关闭时。
MySQL直接读写其实是操作的内存数据页,这些内存数据页被Buffer Pool所管理。Buffer Pool除了能让数据的更新操作更快,也能让数据的读取速度更快。和我们常见的cache一样,MySQL会首先从Buffer Pool读取数据,所以发现数据页缺页,就会从磁盘load数据页到Buffer Pool。为了提高Buffer Pool命中率,Buffer Pool在因为加载新数据页而空间不足时,就需要淘汰掉某些数据页。InnoDB管理Buffer Pool使用了LRU算法。普通的LRU算法这里就再详细介绍了,但是在MySQL的场景中,LRU算法可能存在一个问题:假如我现在查询的结果包含了大量数据,这些数据可能把Buffer Pool的数据都淘汰掉,但这个查询的结果可能只用一次就不再用到了,这样会导致Buffer Pool命中率下降且频繁换页。
InnoDB按照5:3的比例将LRU队列分为了young区和old区。上图的LRU old指向的是old区的起始位置,是整个链表的5/8处。优化之后的LRU算法如下:
1. 如果访问的数据在young区,将数据插入到链表头部,返回数据。
2. 如果访问的数据在old区,如果数据待在old区的时间超过了参数(innodb_old_blocks_time)设定的时间,将数据插入到链表头部,然后返回数据;否则,数据所处的位置不变,返回数据。
3. 如果访问的数据在LRU队列中不存在,则将数据插入到LRU old的位置,即old区的起始位置,并返回数据。
通过上述这种策略,就可以保证在查询大量数据时,不会导致Buffer Pool命中率急剧下降。
在mysql 5.6之前,主从同步是单线程复制, 5.6之后,支持多线程复制,提高了复制效率。
多版本并发控制(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式。
InnoDB为了实现事务的隔离,需要在每个事务启动时保存一个“数据快照”,而这个数据快照不可能真的把整个数据拷贝一份。因此这里使用了一个“技巧”:版本号。InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。
MVCC 在每行记录后面都保存着两个隐藏的列,用来存储两个版本号:(1)创建版本号:创建一行数据时,将当前系统版本号作为创建版本号赋值;(2)删除版本号:删除一行数据时,将当前系统版本号作为删除版本号赋值。如果该快照的删除版本号大于当前事务版本号表示该快照有效,否则表示该快照已经被删除了。
这里还需要定义两个概念“快照读”和“当前读”。快照读是指事务开始时保存了一个快照,后续读取的数据都是取自这个快照;当前读是指读取数据时需要检查数据当前的最新值,必要时还需要加锁。