你分得清MySQL普通索引和唯一索引了吗?( 二 )


change buffer用的是buffer pool里的内存 , 因此不能无限增大 。 change buffer的大小 , 可通过参数innodb_change_buffer_max_size动态设置 。 参数设置为50时 , 表示change buffer的大小最多只能占用buffer pool的50% 。
理解了change buffer机制 , 看看要在这张表中插入一个新记录(4,400) , InnoDB处理流程 。
分情况讨论该记录要更新的目标页是否在内存中:
在内存

  • 唯一索引找到3和5之间位置 , 判断到没有冲突 , 插入值 , 语句执行结束 。
  • 普通索引找到3和5之间位置 , 插入值 , 语句执行结束 。 普通索引和唯一索引对更新语句性能影响的差别 , 只是一个判断 , 只会耗费微小CPU时间 。
不在内存
  • 唯一索引需要将数据页读入内存 , 判断到没有冲突 , 插入值 , 语句执行结束
  • 普通索引将更新记录在change buffer , 语句执行结束
将数据从磁盘读入内存涉及随机IO访问 , 是数据库里面成本最高操作之一 。 change buffer因减少随机磁盘访问 , 所以对更新性能提升明显 。
问题案例:某业务的库内存命中率突然从99%降低到了75% , 整个系统处于阻塞状态 , 更新语句全部堵住 。 探究其原因 , 发现该业务有大量插入数据操作 , 而DBA在前天把其中的某个普通索引改成了唯一索引 。
change buffer的使用场景普通索引的所有场景 , 使用change buffer都可加速吗?
因为merge才是真正进行数据更新时刻;change buffer主要目的是将记录的变更动作缓存下来;所以在一个数据页做merge前 , change buffer记录变更越多(即该数据页上要更新的次数越多) , 收益越大 。
对写多读少业务 , 页面在写完后马上被访问到的概率较小 , change buffer使用效果最好 。 该类业务模型常见为账单、日志类的系统 。
反之 , 假设一业务的更新模式是写后马上查询 , 那么即使满足条件 , 将更新先记录在change buffer , 但之后由于马上要访问该数据页 , 立即触发merge 。 这样随机访问IO的次数不会减少 , 反而增加change buffer维护代价 。 所以 , 对于这种业务模式 , change buffer起副作用 。
4 实践中的索引选择普通索引和唯一索引如何选择 。 这两类索引在查询性能上没差别 , 主要考虑对更新性能影响 。 所以 , 推荐尽量选择普通索引 。
如果所有更新后面 , 都紧跟对该记录的查询 , 那么该关闭change buffer 。 而在其他情况下 , change buffer都能提升更新性能 。 普通索引和change buffer的配合使用 , 对于数据量大的表的更新优化还是很明显的 。
在使用机械硬盘时 , change buffer机制的收效非常显著 。 所以 , 当你有一个类似“历史数据”的库 , 并且出于成本考虑用机械硬盘时 , 应该关注这些表里的索引 , 尽量使用普通索引 , 把change buffer 开大 , 确保“历史数据”表的数据写速度 。
5 change buffer 和 redo logWAL 提升性能的核心机制 , 也是尽量减少随机读写 , 这两个概念易混淆 。 所以 , 这里我把它们放到了同一个流程里来说明区分 。
在表上
5.1 执行插入insert into t(id,k) values(id1,k1),(id2,k2);假设当前k索引树的状态 , 查找到位置后k1所在数据页在内存(InnoDB buffer pool) , k2所在的数据页不在内存中
  • 带change buffer的更新状态图 。
该更新语句涉及四部分:
  • 内存
  • redo log(ib_log_fileX)
  • 数据表空间(t.ibd)
  • 系统表空间(ibdata1)
该更新语句做了如下操作(按图中数字顺序):
  1. Page1在内存 , 直接更新内存
  2. Page2没有在内存中 , 就在内存的change buffer区 , 记录下“我要往Page2插一行”的信息
  3. 将前两个动作记入redo log(图中的3和4)
做完上面 , 事务完成 。 执行这条更新语句的成本很低 , 就写两处内存 , 然后写一处磁盘(两次操作合在一起写了一次磁盘) , 还是顺序写 。
图中两个虚箭 , 是后台操作 , 不影响更新的响应时间 。
这之后的读请求 , 怎么处理?现在执行
select * from t where k in (k1, k2)若读语句紧随在更新语句后 , 内存中的数据都还在 , 那么此时这俩读操作就与系统表空间(ibdata1)和 redo log(ib_log_fileX)无关 。 所以在图中就没画这俩 。
  • 两个读请求的流程图(带change buffer的读过程)
从图中可见:读Page1时 , 直接从内存返回 。 WAL之后如果读数据 , 是不是一定要读盘 , 是不是一定要从redo log里面把数据更新以后才可以返回?其实不用 。 看上图状态 , 虽然磁盘上还是之前数据 , 但这里直接从内存返回结果 , 结果正确 。