保证一致性
从数据库层面,数据库通过原子性、隔离性、持久性来保证一致性。
从应用层面,通过代码判断数据库数据是否有效,然后决定回滚还是提交数据
事务的隔离性
redo log、binlog、两阶段提交这些功能的实现保证了单个事务的原子性以及持久性。
隔离性就是为了多个事务同时执行而存在的
MySQL日志
错误日志:记录出错信息,也记录一些警告信息或者正确的信息。
查询日志:记录所有对数据库请求的信息,不论这些请求是否得到了正确的执行。
慢查询日志:设置一个阈值,将运行时间超过该值的所有SQL语句都记录到慢查询的日志文件中。
数据库级别日志binlog
逻辑日志,也叫归档日志 (binary log)
- 主库中保存所有更新事件日志的二进制文件。
binlog是MySQL 服务层的日志, binlog日志是持续追加写入的。
常用来进行数据恢复、数据库复制,常见的mysql主从架构,就是采用slave同步master的binlog实现的
日志格式的种类
- 基于段的日志格式
binlog_format=STATEMENT , (statement-based replication,SBR)[默认, 最古老]
每一条会修改数据的sql都会记录在binlog中。
优点:
不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。
- 技术成熟, binlog文件较小
- binlog可以用于实时的还原,而不仅仅用于复制
- 主从版本可以不一样,从服务器版本可以比主服务器版本高
相比row能节约多少性能 与日志量,这个取决于应用的SQL情况,正常同一条记录修改或者插入row格式所产生的日志量还小于Statement产生的日志量,但是考虑到如果带条 件的update操作,以及整表删除,alter表等操作,ROW格式会产生大量日志,因此在考虑是否使用ROW格式日志时应该跟据应用的实际情况,其所 产生的日志量会增加多少,以及带来的IO性能问题。
缺点:
- 不是所有的UPDATE语句都能被复制,尤其是包含不确定操作的时候
- 复制需要进行全表扫描(WHERE 语句中没有使用到索引)的 UPDATE 时,需要比 RBR 请求更多的行级锁
- 对于一些复杂的语句,在从服务器上的耗资源情况会更严重,而 RBR 模式下,只会对那个发生变化的记
录产生影响 - 数据表必须几乎和主服务器保持一致才行,否则可能会导致复制出错
- 执行复杂语句如果出错的话,会消耗更多资源
- 必须记录上下文信息,保证语句在从服务器上的执行结果和在主服务器上相同。
- 特定函数如UUID,USER()这样非确定性的函数无法复制。
- 可能造成mysql复制的主备服务器数据不一致,从而中断复制链路。
显示binlog 格式
show variables like ‘binlog_format’;
set session binlog_format=statement;
- 基于行的日志格式 RBR (官方推荐这种格式)
(row-based replication,RBR)
将my.ini 二进制格式修改为binlog_format=ROW
不记录sql语句上下文相关信息,仅保存哪条记录被修改。 row格式会记录行的内容,记两条,更新前和更新后。
优点:
- 任何情况都可以被复制,这对复制来说是最安全可靠的
- 和其他大多数数据库系统的复制技术一样
多数情况下,从服务器上的表如果有主键的话,复制就会快了很多
binlog中可以不记录执行的sql语句的上下文相关的信息,仅需要记录那一条记录被修改成什么了。所以 rowlevel的日志内容会非常清楚的记录下 每一行数据修改的细节。而且不会出现某些特定情况下的存储过 程,或function,以及trigger的调用和触发无法被正确复制的问题
1.使mysql主从复制更加安全。
2.对每一行数据修改比基于段的复制高效。如果误操作修改了数据库中的数据,同时没有备份可以恢复时,我们就可以通过分析二进制日志,对日志中记录的数据修改操作做反向处理的方式来达到恢复数据的目的。
缺点:
记录日志量较大binlog 大了很多
- 复杂的回滚时 binlog 中会包含大量的数据
- 主服务器上执行 UPDATE 语句时,所有发生变化的记录都会写到 binlog 中,而 SBR 只会写一次,这会
导致频繁发生 binlog 的并发写问题 - 无法从 binlog 中看到都复制了写什么语句
binlog_row_image=[full,minimal,noblob]
full : 记录列的所有修改;minimal :只记录修改的列。noblob :如果是text类型或clob字段,不记录 这些日志。
使用 mysqlbinlog -vv ../data/mysql-bin.000005 查看明细日志。
set session binlog_row_image=minimal
- 混合日志格式 MBR
(mixed-based replication,MBR)
binlog_format=MIXED
特点:根据sql语句由系统决定在记录段和基于行的日志格式中进行选择。数据量大小由所执行的SQL决定。
如何选择二进制格式
建议binlog_formart =mixed or binlog_format=row; binlog_row_image=minimal;
binlog日志也有一个参数sync_binlog,默认值也是1,表示每次事务的 binlog 都持久化到磁盘。这样可以保证MySQL异常重启之后binlog日志不丢失。
主从同步也是依赖于binlog日志,所以sync_binlog一定要设置为1,否则主从同步延迟是一个问题。
- 全局事务标识符 GTID
(Global Transaction Identifier,GTID)
update修改数据与原数据相同会再次执行吗?
在 binlog_format=row 和 binlog_row_image=FULL 时,
由于MySQL 需要在 binlog 里面记录所有的字段,所以在读数据的时候就会把所有数据都读出来,但是引擎发现值与原来相同,不更新,直接返回。
Innodb事务日志
事务日志可以帮助提高事务的效率。使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到磁盘。
事务日志采用的是追加的方式,因此写日志的操作是磁盘上一小块区域内的顺序I/O,而不像随机I/O需要在磁盘的多个地方移动磁头,所以采用事务日志的方式相对来说要快得多。
事务日志持久以后,内存中被修改的数据在后台可以慢慢地刷回到磁盘。目前大多数存储引擎都是这样实现的,我们通常称之为预写式日志(Write-Ahead Logging),修改数据需要写两次磁盘。
如果数据的修改已经记录到事务日志并持久化,但数据本身还没有写回磁盘,此时系统崩溃,存储引擎在重启时能够自动恢复这部分修改的数据。
两阶段提交 思想(一致性)
update table_test set num = num+1 where id = 1;

- MySQL执行器先找InnoDB引擎读取id=1这一行的数据,InnoDB引擎直接用树查找主键id=1那条数据,如果数据所在页直接在内存中,那么直接返回,否则先从磁盘读取到缓存中再返回;
- 执行器获得数据后,执行num = num + 1,再调用InnoDB引擎接口写入新的数据;

- InnoDB引擎将新的数据更新到内存中,再将这个更新操作记录到redo log中,此时redo log日志处于prepare状态,并通知执行器已就绪,随时可以提交事务;
- MySQL执行器写入binlog日志并持久化到磁盘
- 调用InnoDB引擎的事务提交接口,InnoDB引擎将刚刚写入的redo log日志的状态改为commit状态,至此,事务完成。
两阶段提交一定是成功的写入了两个日志文件:redo log & binlog,只有这样事务才能提交,数据才能满足一致性原则。
mysql数据库怎么进行崩溃恢复的?
1> 崩溃恢复时,扫描最后一个Binlog文件,提取其中的xid;
2> InnoDB维持了状态为Prepare的事务链表,将这些事务的xid和binlog中记录的xid做比较,如果在binlog中存在,则提交,否则回滚事务。
1. redo log (保证持久性) InnoDB独有
redo log是重做日志(物理日志),提供前滚操作
记录的是:记录数据修改之后的值, 不管事务是否提交都记录下来
MySQL的WAL(Write-Ahead Logging)技术, 即先写日志,再写磁盘。
当MySQL执行一条更新语句的时候,InnoDB引擎会把记录先写到redo log文件中,并更新内存。
在事务提交时进行一次flush操作,保存到磁盘中。
当数据库或主机失效重启时,会根据redo log进行数据的恢复,如果redo log中有事务提交,则进行事务提交修改数据。
redo log写日志的具体实现:
指定一块固定大小的磁盘空间,例如4G,并分成4个文件,从头开始写,写到末尾再回到开头继续循环写,再次从头开始写之前,需要将即将覆盖的文件内容更新到数据文件中,然后再擦出这块内容,腾出空间写入新的redo log,如此往复。
MySQL中有一个参数innodb_flush_log_at_trx_commit,默认值是1,代表着每一次的redo log都持久化到磁盘。
这样就可以保证即使数据库发生异常宕机,重启后也可以恢复之前的数据记录,这个能力有一个专有的名词:crash-safe。
采用redo log的好处?
其实好处就是将redo log进行刷盘比对数据页刷盘效率高,具体表现如下
redo log体积小,毕竟只记录了哪一页修改了啥,因此体积小,刷盘快。
redo log是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快。
2. Undo log (保证原子性)
undo log名为回滚日志, 保存事务发生之前的数据版本
是实现原子性的关键,当事务回滚时能够撤销所有已经成功执行的sql语句,他需要记录你要回滚的相应日志信息, 用于数据的撤回操作,它记录了修改的反向操作
比如,插入对应删除,修改对应修改为原来的数据,通过undo log可以实现事务回滚,并且可以根据undo log回溯到某个特定的版本的数据,实现MVCC
当事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。
- insert undo log
代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
- update undo log
事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便 删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除
purge 为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下老记录的deleted_bit,并不真正将过时的记录删除。
为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。为了不影响MVCC的正常工作,purge线程自己也维护了一个read view(这个read view相当于系统中最老活跃事务的read view);如果某个记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。
中继日志(主从复制)
单台MySQL数据库服务器不能满足实际的需求。此时数据库集群解决了这个问题。
采用MySQL分布式集群,能够搭建一个高并发、负载均衡的集群服务器。在此之前我们必须要保证每台MySQL服务器里的数据同步。数据同步主要有主从复制和主主复制。
MySQL数据库自身提供的主从复制功能可以方便的实现数据的多处自动备份,实现数据库的拓展。多个数据备份不仅可以加强数据的安全性,通过实现读写分离还能进一步提升数据库的负载性能。

- 做数据的热备
- 架构的扩展。业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能。
原理
MySQL的主从复制是一个异步的复制过程
前提
- 打开Master端的binlog记录功能
- 在Slave服务器上执行start slave命令开启主从复制开关,开始进行主从复制。

数据将从一个MySQL数据库(Master)复制到另一个MySQL数据库(Slave),在Master和Slave之间实现整个主从复制的过程是由三个线程参与完成的。
- 主:binlog线程——记录下所有改变了数据库数据的语句,写入的binlog;
- 从:io线程——在使用start slave 之后,负责从master上拉取 binlog日志,放进 自己的relay log(中继日志)中;
- 从:sql执行线程——执行relay log(中继日志)中的语句;
详细过程:
(2)此时,Slave服务器的IO线程会通过在master上已经授权的复制用户权限请求连接Master服务器,并请求从执行binlog日志文件中的指定位置(日志文件名和位置就是在配置主从复制服务时执行change master命令指定的)之后开始发送binlog日志内容。
(3)Master服务器接收来自Slave服务器的IO线程的请求后,其上负责复制的IO线程会根据Slave服务器的IO线程请求的信息分批读取指定binlog日志文件指定位置之后的binlog日志信息,然后返回给Slave端的IO线程。返回的信息中除了binlog日志内容外,还有在Master服务器端记录的IO线程。返回的信息中除了binlog中的下一个指定更新位置。
(4)当Slave服务器的IO线程获取到Master服务器上IO线程发送的日志内容、日志文件及位置点后,会将binlog日志内容依次写到Slave端自身的Relay Log(即中继日志)文件(Mysql-relay-bin.xxx)的最末端,并将新的binlog文件名和位置记录到master-info文件中,以便下一次读取master端新binlog日志时能告诉Master服务器从新binlog日志的指定文件及位置开始读取新的binlog日志内容
(5)Slave服务器端的SQL线程会实时检测本地Relay Log 中IO线程新增的日志内容,然后及时把Relay LOG 文件中的内容解析成sql语句,并在自身Slave服务器上按解析SQL语句的位置顺序执行应用这样sql语句,并在relay-log.info中记录当前应用中继日志的文件名和位置点
主从同步延时
原因
- 主库并发高会导致写操作不断写入 binlog,对于 SQL 线程说可能会应接不暇,也会产生主从延迟。
- 重放过程中如果遇到锁等待也是产生延迟的原因之一。
网络延迟
一般的做法是,使用多台slave来分摊读请求,再从这些slave中取一台专用的服务器,只作为备份用,不进行其他任何操作,就能相对最大限度地达到’实时’的要求了
slave_net_timeout单位为秒 默认设置为 3600秒
参数含义:当slave从主数据库读取log数据失败后,等待多久重新建立连接并获取数据
master-connect-retry单位为秒 默认设置为 60秒
参数含义:当重新建立主从连接时,如果连接建立失败,间隔多久后重试。
通常配置以上2个参数可以减少网络问题导致的主从数据同步延迟
随机重放
从库中 SQL 线程重放的过程是随机写盘的,并且 SQL 线程是单线程的,因此数据来不及重放的话就会导致主从延迟。
mysql-5.6.3已经支持了多线程的主从复制(并行复制)。原理和丁奇的类似,丁奇的是以表做多线程,Oracle使用的是以数据库(schema)为单位做多线程,不同的库可以使用不同的复制线程。
主库并发高
某一时刻,大量写请求打到主库上,意味着要不断对 binlog 进行写入,此时从库中的 SQL 线程就会应接不暇,自然会产生主从延迟。
对于主库并发高的情况,这种方式你只能通过控制并发来解决延迟了,多用用 Redis。
锁等待
对于 SQL 单线程来说,当遇到阻塞时就会一直等待,直到执行成功才会继续进行。如果某一时刻从库因为查询产生了锁等待的情况,此时只有当前的操作执行完成后才会进行下面的操作,同理也就产生了主从延迟的情况。
读主库 这种情况你肯定不陌生,对于一些实时性要求比较高的数据,你总不能读从库去拿吧,万一延迟个大半天,你不得贡献自己的年终奖啊。
方案一:忽略
任何脱离业务的架构设计都是耍流氓,绝大部分业务,例如:百度搜索,淘宝订单,QQ消息,58帖子都允许短时间不一致。
方案二:强制读主
如上图:
(1)使用一个高可用主库提供数据库服务
(2)读和写都落到主库上
(3)采用缓存来提升系统读性能
这是很常见的微服务架构,可以避免数据库主从一致性问题。
方案三:选择性读主
强制读主过于粗暴,毕竟只有少量写请求,很短时间,可能读取到脏数据。
有没有可能实现,只有这一段时间,可能读到从库脏数据的读请求读主,平时读从呢?
如上图,当写请求发生时:
(1)写主库
(2)将哪个库,哪个表,哪个主键三个信息拼装一个key设置到cache里,这条记录的超时时间,设置为“主从同步时延”
画外音:key的格式为“db:table:PK”,假设主从延时为1s,这个key的cache超时时间也为1s。
如上图,当读请求发生时:
这是要读哪个库,哪个表,哪个主键的数据呢,也将这三个信息拼装一个key,到cache里去查询,如果,
(1)cache里有这个key,说明1s内刚发生过写请求,数据库主从同步可能还没有完成,此时就应该去主库查询
(2)cache里没有这个key,说明最近没有发生过写请求,此时就可以去从库查询
以此,保证读到的一定不是不一致的脏数据。
参考文章:
- MySQL主从复制及读写分离实战 https://blog.csdn.net/why15732625998/article/details/80463041


