6 change buffer 和 redo log
WAL 提升性能的核心机制,也是尽量减少随机读写,它们有啥区别呢?
插入流程
insert into t(id,k) values(id1,k1),(id2,k2);
假设当前k索引树的状态,查找到位置后:
- k1所在数据页在内存(InnoDB buffer pool)
- k2数据页不在内存
看如下流程:
带change buffer的更新流程
图中箭头都是后台操作,不影响更新的响应。
该更新做了如下操作:
Page1在内存,直接更新内存
Page2不在内存,就在change buffer区,缓存一个“往Page2插一行记录”的信息
将前两个动作记入redo log
之后事务完成。执行该更新语句成本很低,只写两处内存,然后写一处磁盘(前两次操作合在一起写了一次磁盘),还是顺序写。
处理之后的读请求
select * from t where k in (k1, k2);
读语句紧随更新语句,内存中的数据都还在,所以此时这俩读操作就与系统表空间和 redo log 无关。
带change buffer的读过程
读Page1时,直接从内存返回。
WAL之后如果读数据,是不是一定要读盘,是不是一定要从redo log把数据更新之后才可以返回?
其实不用。看上图状态,虽然磁盘上还是之前数据,但这里直接从内存返回结果,结果正确。
要读Page2时,需把Page2从磁盘读入内存,然后应用change buffer里的操作日志,生成一个正确版本并返回结果。可见直到需读Page2时,该数据页才被读入内存。
综上,这俩机制的更新性能:
redo log 主要节省随机写磁盘的I/O消耗(转成顺序写)
change buffer主要节省随机读磁盘的I/O消耗
7 总结
由于唯一索引用不了change buffer,若业务可以接受,从性能角度,优先考虑非唯一索引。
到底何时使用唯一索引
问题在于“业务可能无法确保”。本文前提是“业务代码已经保证不会写入重复数据”,才讨论性能问题。
如果业务不能保证或业务就是要求数据库来做约束
没得选,必须创建唯一索引。那本文意义的在于,如果碰上大量插入数据慢、内存命中率低时,多提供了一个排查思路。
“归档库”场景,可考虑使用唯一索引
比如,线上数据只需保留半年,然后历史数据保存在归档库。此时,归档数据已是确保没有唯一键冲突。要提高归档效率,可考虑把表的唯一索引改普通索引。
若某次写入使用了change buffer,之后主机异常重启,是否会丢失change buffer数据
不会丢失。
虽然是只更新内存,但在事务提交时,change buffer的操作也被记录到了redo log。
所以崩溃恢复时,change buffer也能找回。
merge时是否会把数据直接写回磁盘
merge流程
- 从磁盘读入数据页到内存(老版本数据页)
- 从change buffer找出该数据页的change buffer 记录(可能多个),依次应用,得到新版数据页
- 写redo log
该redo log包含数据的变更和change buffer的变更
至此merge结束。
这时,数据页和内存中change buffer对应磁盘位置都尚未修改,是脏页,之后各自刷回自己物理数据,就是另外一过程。
问题思考
在构造第一个例子的过程,通过session A的配合,让session B删除数据后又重新插入一遍数据,然后就发现explain结果中,rows字段从10001变成37000多。
而如果没有session A的配合,只是单独执行delete from t 、call idata()、explain这三句话,会看到rows字段其实还是10000左右。这是什么原因呢?
如果没有复现,检查
隔离级别是不是RR(Repeatable Read,可重复读)
创建的表t是不是InnoDB引擎
为什么经过这个操作序列,explain的结果就不对了?
delete 语句删掉了所有的数据,然后再通过call idata()插入了10万行数据,看上去是覆盖了原来10万行。
但session A开启了事务并没有提交,所以之前插入的10万行数据是不能删除的。这样,之前的数据每行数据都有两个版本,旧版本是delete之前数据,新版本是标记deleted的数据。
这样,索引a上的数据其实有两份。
不对啊,主键上的数据也不能删,那没有使用force index的语句,使用explain命令看到的扫描行数为什么还是100000左右?(潜台词,如果这个也翻倍,也许优化器还会认为选字段a作为索引更合适)
是的,不过这个是主键,主键是直接按照表的行数来估计的。而表的行数,优化器直接用的是show table status的值。
大家的机器如果IO能力比较差的话,做这个验证的时候,可以把innodb_flush_log_at_trx_commit 和 sync_binlog 都设成0。
参考
https://dev.mysql.com/doc/refman/8.0/en/innodb-change-buffer.html