
1、MySQL 如果发生异常重启,是怎么保证数据完整性的?
在两阶段进行提交数据的不同时刻,MySQL异常重启会出现什么现象?写入 redo log ,处于 prepare 阶段之后,写 binlog 之前发生crash? 因为此时 binlog 还没有写, redo log 还没有提交,所以在崩溃恢复的时候,这个事务会回滚。这时,binlog 也还没有写,所以从库也不会进行读取。
写入 binlog,redo log 还没有 commit 之前发生crash,那么崩溃回复时MySQL如何处理? 崩溃恢复时的判断规则是:
时刻 二 就是发生 crash 是在第二种情况,崩溃恢复过程中事务会被提交。 MySQL 是如何知道 binlog 中是完整的呢?
1:Statement 格式的binlog, 最后有 COMMIT; 2:Row 格式的binlog,最后会有一个 XID event; 3:Mixed 格式的binlog, 是上面两种的混合使用,一般语句修改使用 Statement格式;如一些函数,statement 无法完成主从复制的操作,则采用 row 格式保存binlog。 在MySQL 5.6.2 版本后,引入了binlog-checksum参数,用来验证binlog内容的正确性。
MySQL 中 redo log 和 binlog 是怎么关联起来的?
它们之间有一个共同的数据字段,叫 XID。崩溃的时候,会按顺序扫描redo log:
1:如果碰到有prepare、又有 commit 的 redo log,则直接提交;
2:如果碰到只有prepare、没有 commit 的 redo log,通过 XID 去binlog 中找对应的事务。
处于 prepare 阶段的 redo log 加上完整的 binlog ,重启能恢复,MySQL 为什么这样设计?
答:这个问题跟数据与备份的一致性有关,在 binlog 写完之后 MySQL 发生崩溃,这时候 binlog 已经写入了,之后就会被从库使用。采用这个策略,主库也要提交这个事务,主库和备库的数据就保持了一致。
为什么还需要两阶段提交呢?干脆先写完 redo log,再写 binlog。崩溃恢复的时候,必须得两个日志都完整才可以? redo log 和 binlog 是两个独立的逻辑。 如果这时候先写redo log, binlog 还没有写的时候,MySQL 异常重启 或者 crash了。这个时候,数据是可以恢复回来的。但是由于binlog 的记录还没有写,这时候binlog里面没有操作的记录,在进行备份的时候,就会出现不一样的情况。 如果这时候先写binlog, 再写redo log。写完binlog 发生崩溃,崩溃恢复这个事务是无效的,所以数据回滚了。但是binlog 里 已经记录了值,所以在之后用 binlog 进行数据恢复的时候,会多一个事务出来,会造成与原库不一致的情况。
也就是说, 对于InnoDB 引擎来说,如果 redo log 提交完成了,事务就不能回滚了(如果这里允许回滚,可能覆盖掉别的事务的更新)。而如果 redo log 直接提交,然后 binlog 写入的失败, InnoDB 又回滚不了,数据和binlog 日志又不一致了。
只保留binlog,是不是也能提供崩溃恢复的能力?
是不可以的。因为InnoDB并不是MySQL原生存储引擎。myisam 在设计之初并没有支持崩溃恢复。如果说只保留binlog 流程就变成了:… -> “数据更新到内存” -> “写 binlog” -> “提交事务”。 举个例子:现在有两个事物,binlog1写完进行提交了,binlog2在进行 commit 前发生 crash 了。重启之后,引擎内部 事物2会进行回滚,然后应用 binlog2进行恢复。但是对于事物1来说,系统认为已经完成了,不会在应用一次binlog1. 也就是说:如果在事务2 commit 前发生崩溃,事务1会可能丢失,而且是数据页级的丢失。因为innodb引擎使用的是WAL技术,执行事务的时候,写完内存和日志,事务就算完成了。如果崩溃,要依赖于日志进行恢复数据页。 如果是在崩溃恢复的角度来讲是可以的。因为崩溃恢复是 redo log 的功能,但是此时没有两阶段提交,系统依然是crash-safe的。
binlog 有redo log 无法替代的功能,redolog是循环写,无法进行归档。
正常运行的实例,数据写入最后的落盘,是从redo log 过来的还是 buffer pool 更新过来的? redo log 里面有的是 记录在某个数据页上做了什么修改,而不是“这个数据修改后的最新值”。redo_log其实并不是真正的物理日志,也是记录的数据页的逻辑日志,也是要根据之前的数据算出现在的数据的。因为如果是物理日志,那么原数据是200M,那么redo_log就得记录200M。所以redo_log实际上是逻辑和物理的组合,取物理逻辑各自的优点。 所以说redo log 并没有记录数据页的完整数据,所以他没有能力自己去更新磁盘数据页,也不存在数据最终落盘是由redo log 更新过去的。 1、如果是正常运行的实例,数据页被修改之后,跟磁盘数据页不一致,成为脏页,最终数据落盘,是把内存中的数据写入磁盘。这个过程和redo log 一点关系都没有 2、在崩溃恢复场景中, InnoDB如果判断到一个数据页可能在崩溃恢复的时候丢失了更新,就会将他读到内存,然后让 redo log 更新内存内容。更新完成后,内存页变成脏页,就回到了第一种正常运行的状态。
redo log buffer 是什么?先修改内存,还是先写 redo log 文件? 在更改数据过程中,生成的日志都得先保存起来,但又不能在事务还没有 commit 的时候,写入到 redo log 文件里。所以 redo log buffer 就是一块内存,用来存 redo 日志的。也就是说,在执行操作的时候,数据的内存被修改了, redo log buffer 也写入了日志。但是真正将 redo 日志 写到 redo log 文件中, 是在执行 commit 语句的时候做的。 基于常用的 MyISAM 和 InnoDB 引擎来看,MyISAM 引擎在没有任何where条件下,是将一个表的总行数存放在磁盘上的,在使用count(*)时直接返回结果,效率很高。而InnoDB 引擎是把数据一行一行的读出来,然后累计计数。
InnoDB 为什么不跟 MyISAM一样存储这个数? 因为 MVCC(多版本控制)的原因,应该返回多少行数据,InnoDB也不确定。
InnoDB 的索引树,主键索引树叶子节点保存的是数据,普通索引树保存的是主键id,普通索引树要比主键索引树小。 对于count(*)这样的操作,遍历哪个索引树得到的结果逻辑上都是一样的。索引,MySQL优化器会找到最小的那棵树进行遍历。在保证逻辑正确的前提下,尽量减少扫描的数据量,是数据库系统设计的通用法则之一。
在 InnoDB 中 count(*), count(1), count(字段), count(主键id) 有什么区别? 性能哪个好一些? count() 是一个聚合函数,对于返回的结果集,一行行的判断,如果是count()函数的参数不是null,累计就加1。否则就不加,最后返回累计值。
count(*), count(1), count(主键) 都表示返回满足条件结果集的总行数;
count(字段) 表示返回满足条件的数据行里面, 参数 "字段" 不为 NULL 的总个数。
count(主键id): InnoDB 引擎会遍历整张表, 把每一行的id 取出来, 返给server层进行判断不为空, 按行累加;
count(1): InnoDB 引擎遍历整张表, 但是不取值. server层对返回的每一行, 放一个数字进去, 不可能为空, 按行累加;
count(字段):
1、如果这个字段定义为not null的话,一行行从记录取出这个字段, 判断不为null, 按行累计
2、如果这个字段定义为null, 执行的时候判断可能为null, 还要把值取出来在判断一下, 不是null 才累加
count(*): 是个例外, 他不取值, count(*)肯定不是null, 按行累加 . 可以理解为 count(*) 就是遍历主键或者row id, 有则加一,没有停止统计.
效率的顺序依次是 count(字段) < count(主键id) < count(1)≈count(*)
最后,求关注。如果你还想看更多优质原创文章,欢迎关注我的公众号「白砂」。
如果我的文章对你有所帮助,还请帮忙点赞、在看、转发一下,你的支持会激励我输出更高质量的文章,非常感谢!