天天看点

MySQL普通索引和唯一索引到底什么区别?(下)6 change buffer 和 redo log7 总结问题思考

6 change buffer 和 redo log

WAL 提升性能的核心机制,也是尽量减少随机读写,它们有啥区别呢?

插入流程

insert into t(id,k) values(id1,k1),(id2,k2);      

假设当前k索引树的状态,查找到位置后:

  • k1所在数据页在内存(InnoDB buffer pool)
  • k2数据页不在内存

看如下流程:

带change buffer的更新流程

图中箭头都是后台操作,不影响更新的响应。
MySQL普通索引和唯一索引到底什么区别?(下)6 change buffer 和 redo log7 总结问题思考

该更新做了如下操作:

Page1在内存,直接更新内存

Page2不在内存,就在change buffer区,缓存一个“往Page2插一行记录”的信息

将前两个动作记入redo log

之后事务完成。执行该更新语句成本很低,只写两处内存,然后写一处磁盘(前两次操作合在一起写了一次磁盘),还是顺序写。

处理之后的读请求

select * from t where k in (k1, k2);      

读语句紧随更新语句,内存中的数据都还在,所以此时这俩读操作就与系统表空间和 redo log 无关。

带change buffer的读过程

MySQL普通索引和唯一索引到底什么区别?(下)6 change buffer 和 redo log7 总结问题思考

读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流程

  1. 从磁盘读入数据页到内存(老版本数据页)
  2. 从change buffer找出该数据页的change buffer 记录(可能多个),依次应用,得到新版数据页
  3. 写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