维护市民系统,按照身份证号查姓名select name from CUser where id_card = 'xxxxxxxyyyyyyzzzzz';
身份证号字段大,不建议做主键: id_card 创建唯一索引?普通索引?
非主键都是二级索引:包括唯一索引和普通索引
一、查询过程(性能影响)
select id from T where k=5。索引树上查找,从树根开始(B+ 树),按层搜索到叶子节点,图中右下角的这个数据页,数据页内部二分法定位
普通索引:查找满足记录 (5,500) 后,查找下一个,直到不满足 k=5 记录。
唯一索引:唯一性,查到停止。
性能差距微乎其微:
InnoDB 页为单位,整体读入内存。读时,不从磁盘读,数据页默认16KB。
找到 k=5 时,数据页都在内存里。普通索引多做“查找、判断下一条记录”,一次指针寻找和一次计算。
如 k=5 是数据页最后记录,下页取下一个记录,稍微复杂。(概率低:整型字段,数据页可近千个 key)
二、更新过程
如数据页在内存中直接更新,否则InooDB 将更新操作缓存 change buffer 中(不影响一致性),不需从磁盘读入。
change buffer:可持久化数据,内存中有拷贝,也会被写入到磁盘上。
下次查询,将数据页读入内存,执行 change buffer 中与这个页有关的操作。保证数据逻辑正确性。
merge:change buffer 中操作应用到原数据页,get最新结果
触发 merge:1、访问数据页,2、后台线程会定期 merge,3、db正常关闭(shutdown)时。
将更新记录change buffer,减少读磁盘,语句执行速度提升。数据读入内存占用 buffer pool ,避免占用内存。
三、什么条件下使用 change buffer ?
只有普通索引用。
唯一索引,更新判断是否违反唯一性约束。插入 (4,400),判断表是否有 k=4 ,数据页读入内存才能判断。更新内存快,没必要用change buffer。
change buffer用buffer pool内存,不能无限增大。innodb_change_buffer_max_size =50 动态设置。最多占用 buffer pool 50%。
3.1插入一个新记录 (4,400) 的话,InnoDB 处理流程:
1、更新目标页在内存中。
唯一索引来说,找到 3 和 5 之间的位置,判断到没有冲突,插入结束;
普通索引来说,找到 3 和 5 之间的位置,插入结束。
差别判断,耗费微小CPU 时间。不是重点。
2、更新的目标页不在内存中
唯一索引:数据页读入内存,判断冲突,插入结束;
普通索引:更新记录在 change buffer,结束。
数据从磁盘读入内存涉及随机 IO 访问,成本高。change buffer减少,性能提升。
库内存命中率突然从 99% 降到 75%,整个系统阻塞,更新语句全部堵住。原因:大量插入数据前一天,普通索引改成了唯一索引。
3.2change buffer 使用场景
普通索引的所有场景, change buffer都加速吗?
merge时是真正更新时,change buffer将记录变更缓存,merge 前,change buffer记录越多收益就越大。
适用:写多读少,写完马上被访问概率小, change buffer 效果最好。如账单类、日志类系统。
不适用:写后马上查,更新记录在 change buffer,马上访问这个数据页,立即触发 merge 过程。随机访问 IO 次数不会少,增加change buffer 维护。
四、索引选择和实践
尽量选择普通索引。查询没差别,更新性能有影响。
更新后马上查,应该关闭 change buffer
“历史数据”库+机械硬盘+change buffer (尽量调大)+ 普通索引= 非常显著。数据量大的表,写入速度快。
五、change buffer 和redo log
之前 redo log 和 WAL。 redo log 和 change buffer容易混淆。WAL 提升性能核心机制,减少随机读写。
mysql> insert into t(id,k) values(id1,k1),(id2,k2);
当前 k 索引树的状态,查找到位置后,k1 数据页在内存 (InnoDB buffer pool) 中,k2 数据页不在内存中。
涉及了四个部分:内存、redo log(ib_log_fileX)、 数据表空间(t.ibd)、系统表空间(ibdata1)。
1. Page 1 在内存中,直接更新内存;
2. Page 2 没有在内存中,就在内存的 change buffer 区域,记录下“我要往 Page 2 插入一行”这个信息
3. 两个动作记入 redo log 中(图 3 和 4)。
事务完成。更新成本,写两内存,一磁盘(两次操作合),还是顺序写的。
虚线:后台操作,不影响更新响应时间。
读请求
select * from t where k in (k1, k2)。
如果发生更新语句后不久,内存中数据还在,与系统表空间(ibdata1)和 redo log(ib_log_fileX)无关
1. Page 1从内存返回。WAL 后读数据,不一定读盘,不一定从 redo log 里面把数据更新以后返回?图 3,磁盘上还是之前数据,内存返回正确结果。
2. Page 2 需要把 Page 2 从磁盘读入内存中,用 change buffer日志,生成正确版本,返回结果。
更新性能:redo log :节省随机 "写 "磁盘的 IO 消耗(转成顺序写),change buffer 节省随机 ''读' 磁盘的 IO 消耗。
小结
普通、唯一索引选择,数据查询、更新过程,
change buffer 机制及应用场景,索引选择的实践。
唯一索引用不上 change buffer 优化,如业务可接受,选非唯一索引(从性能角度)。
补充:业务要求数据库做约束,必须创建唯一索引。普通索引:大量插入数据慢、内存命中率低(归档库,没有唯一键冲突)。
思考题:
1、图 2change buffer开始写内存,机器掉电重启,会不会导致 change buffer 丢失呢? 再从磁盘读入数据没有 merge 过程,等于数据丢失。会不会出现这种情况?
答:不会丢失,事务提交时,change buffer记录到 redo log 里
2、merge过程是否会把数据直接写回磁盘?merge 流程:
1. 从磁盘读入数据页到内存(老版本数据页);
2. 从 change buffer 里找出这个数据页记录,依次应用,得新版数据页;
3. 写 redo log。这个 redo log 包含了数据的变更和 change buffer 的变更。
merge 结束了。数据页和内存中 change buffer 对应的磁盘位置都还没修改,属于脏页,之后各自刷回自己物理数据,就是另外一个过程了。
评论1
1.change buffer一部分内存有一部分在ibdata.
做purge(清除)操作,把change buffer持久化到ibdata
2.redo log里记录了数据页的修改以及change buffer新写入的信息
如果掉电,没有持久化:
(1)change buffer写入,redo log做fsync但未commit, binlog未fsync到磁盘, 丢失
(2)change buffer写入,redo log写入但没commit, binlog以及fsync到磁盘, 先从binlog恢复redo log,再从redo log恢复change buffer
(3)change buffer写入,redo log和binlog都已经fsync.redo log里恢复。
fsync:同步内存已修改文件数据到储存设备。
hit rate,判断内存命中率
ob这个思路做聚簇索引的insert优化,不知道是怎么判断的唯一性?
评论2
1. 数据页读到内存不用加锁
3. purge之后不会产生redo log:
merge从磁盘读数据页到内存,然后应用,更新内存,写redolog
现在内存变脏页,跟磁盘数据不一样。刷脏页不用写。
评论3
change Buffer和数据页,都是物理页组成部分,共享表空间中B+树,默认ibdata1中。
Checkpoint机制:change buffer 写入表空间(保证一致性),和普通表脏页刷新到磁盘一样
change buffer B+树的key是表空间ID,表空间ID 查询change buffer快。
评论4
系统表空间:放系统信息,数据字典,对应磁盘文件ibdata1,
数据表空间:表数据文件,对应的磁盘文件就是 表名.ibd
评论5
真正把数据更新到磁盘,是由change buffer做还是redo log做?
两个都不少,内存直接写到磁盘。