普通索引和唯一索引 场景: 1 2 3 假设维护一个市民系统、每个人有一个唯一的身份证号、经常会根据身份证号查询、sql如下: select name from user where id_card ='xxx' ; 一定会考虑在id_card上建立索引、那么、这个索引应该是唯一索引还是普通索引呢 ?
思考:
id_card字段比较大、不建议作为主键、那么、1.普通索引 2.唯一索引 如何选择?依据又是什么呢 ?
分析: 查询过程
1 2 3 4 5 6 7 8 9 10 11 12 假设执行的语句为 select t from T where k=5 ; 搜索从根开始、按层搜索到叶子节点、可以认为数据页内部通过二分法来定位 1. 对于普通索引、查找到第一条记录(5 , 500 ) 之后、需要查找下一个记录、直到碰到第一个不满足k=5 的记录2. 对于唯一索引】由于定义了唯一性、查找到(5 , 500 ) 之后、停止检索、直接返回那么带来的性能差异呢 ? - 微乎其微 innodb的数据是按照数据页为单位来读写的、即: 当需要读一条记录的时候、不是讲记录本身从磁盘读出、而是以页为单位、 将整个数据页读取到内存、数据页大小默认为16 k 因为是按页读取、当找到k=5 时、它所在的数据页已经在内存了、对于普通索引来说、多做的一次'查找和判断下一条记录' 只需要一次指针查找和一次计算 不幸的是、恰好l=5 是数据页的最后一条记录呢 ?.... 必须读取下一个数据页, 对于整型字段、16 k可以放近千个key、出现这种情况的概率很低、所以计算平均性能差异时、可认为这个操作成本对于现在的CPU来说可以忽略不计
更新过程
change_buffer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 先说下change_buffer的概念: 更新数据页时,若数据页在内存中 -> 直接更新, 不在内存 -> 在不影响一致性读的情况下、会讲更新操作缓存在change_buffer 这样就不需要从磁盘读入数据页, 下次查询需要访问这个数据页时、将数据页读到内存、与change_buffer合并(称为merge) 虽然名字叫change_buffer、实际可持久化、在内存中有copy、也会被写入磁盘 触发merge: 1. 访问数据页2. 后台线程定期merge3. db正常关闭、shutdown4. 达到change_buffer的可用最大内存、触发merge、然后淘汰显然、如果更新操作先记录在change_buffer、减少读磁盘、可以提高sql执行效率 而且、数据读入内存、是需要占用buffer_pool的、这种方式还可以避免占用内存、提高内存使用效率 那么什么时候可以使用buffer_pool? 1. change_buffer 占用 buffer_pool 的内存、通过 innodb_change_buffer_max_size 调整、设为50 、 表示change_buffer 最多占用buffer_pool的50 % 2. 不能无限增大、不能 > buffer_pool3. 对唯一索引来说、所有的更新都要判断操作是否违反唯一约束、 eg. 要插入(4 , 400 )这个记录、必须先判断记录是否存在、必须先将数据页读入内存 若已读入内存、直接更新内存更快、无需使用 change_buffer、 实际上、唯一索引的更新也不能使用change_buffer、只有普通索引可用
理解了 change_buffer、看下插入(4,400) 1 2 3 4 5 6 7 8 9 10 11 12 1. 更新的记录目标页在内存 - 无差别(只有一个判断的差别) 1 ) 唯一索引: 找到3 、5 之间位置、判断无冲突、直接插入 2 ) 普通索引: 找到3 、5 之间、直接插入 2. 更新记录目标页不在内存 1 ) 唯一索引: 将数据页读入内存、判断无冲突、插入 2 ) 普通索引: 将记录更新在change_buffer、执行结束 将数据从磁盘读到内存、涉及io随机访问、change_buffer减少了随意访问磁盘、性能会明显提升 3. 案例: 业务库的内存命中率突然下降、整个系统处于阻塞状态、更新全部阻塞、 深入排查后发现: 业务有大量的插入操作、而、前一天晚上上、将普通索引改成了唯一索引、使写操作的效率下降
change_buffer使用场景 1 2 3 4 5 6 7 change_buffer对于查询无明显效果、也只适用于普通索引、那么 普通索引的所有场景、change_buffer都能起到加速作用么 ? 1. merge 的时候是真正数据更新的时刻、change_buffer主要是将记录变更的动作缓存、so .merge 前变更越多、收益越大 对于写多、读少的业务、change_buffer的效果最好、常见业务模型: 账单类、日志类系统 2. 若业务场景是、写完立马会有查询、由于立马访问数据页、会立即触发merge 、随机访问的io次数不会减少、 反而增加了change_buffer的维护代价、不适合使用
索引选择和实践 1 2 1. 若所有更新后跟着记录的查询、应关闭change_buffer2. 若有一个机械硬盘的历史库、应尽量使用普通索引、调大change_buffer、确保历史数据的写入效率
change_buffer 和 redo log 1 2 3 假设执行更新 insert into t(id , k) values (id1, k1), (id2, k2)当前k索引树的状态、k1所在的数据页在内存(innodb buffer pool)中、
1 2 3 4 5 6 7 8 9 涉及: 内存、redolog(ib_log_fileX)、数据表空间(t.ibd)、系统表空间(ibdata1) 四个部分 更新做了如下操作: 1. page1在内存、直接更新内存 (图中1 )2. page2不在内存、在内存的change_buffer区域、记录下 '往page2插入1行' 这个信息 (图中2 )3. 将1 、2 两个动作写入redo log(图中3 、4 )所以、这条更新写了两处内存、一次磁盘(两次操作合写一次磁盘)还是顺序写、 图中两个虚线、是后台操作、不影响更新的响应时间
读请求
1 2 3 4 5 6 7 8 select * from t where k in (k1, k2)1. 假设、读发生在更新不久、内存中数据还在、此时与系统表空间和redolog无关、直接读即可2. 读page1时、从内存返回、读page2时、需要把page2从磁盘读入内存、然后应用change buffer的操作日志、合并数据so. 简单对比这两个机制在提升更新性能上的收益的话、 redo log 节省的是随机写 IO 的消耗、转为顺序写change buffer节省的是随机读io的消耗
Q: change_buffer一开始是写内存的、此时若掉电重启、会导致change_buffer丢失么 ? 若丢失、从磁盘读入时、就丢了merge、相当于丢了数据…
A:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 虽然只是更新内存、但: 事务提交时、把change buffer 的操作记录到了 redo log . 所以恢复时、change buffer 可以找回 1 .change buffer 有一部分在内存有一部分在ibdata.做purge操作,应该就会把change buffer 里相应的数据持久化到ibdata 2 .redo log 里记录了数据页的修改以及change buffer 新写入的信息如果掉电,持久化的change buffer 数据已经purge,不用恢复。主要分析没有持久化的数据 情况又分为以下几种: (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 里恢复 merge流程: 1 . 从磁盘读入数据页到内存2 . 从change buffer 找出这个数据页的change buffer 记录(可能是多个)、依次应用、得到新版数据页3 . 写redo log . 这个redo log 包含了数据的变更和change buffer 的变更merge流程完成、哈哈、此时 数据页和内存中change buffer 对应的磁盘位置都还没修改、属于脏页、之后各自刷回自己的物理数据 -> 另外一个流程
#####
1 2 3 4 5 6 7 内存命中率: ib_bp_hit=1000 – (t2.iReads – t1.iReads)/(t2.iReadRequest – t1.iReadRequest)*1000 change buffer 相当于推迟更新、对于MVCC是否有影响?比如加锁?锁是单独的数据结构、若数据页上有锁、change buffer 在判断能否使用时、会认为否 change buffer 中、有此行记录的条件下、再次修改、是增加还是原地修改?增加