mysql innodb存储引擎学习 (2)

接上:https://blog.csdn.net/qq_32250495/article/details/99099152

四、表
4.1 索引组织表。在innoDB中,表都是按照主键顺序组织存放的,这种存储方式的表称为索引组织表。innoDB会按照如下方式选择或创建主键:
    首先判断表中是否有非空的唯一索引,如果有则为主键。如果没有则innnoDB自动创建一个6字节大小的指针。

4.2 innoDB逻辑存储结构
    从逻辑上来看,innodb所有的数据都被逻辑的放在一个空间中,称为表空间。表空间又由段、区、页组成。
    表空间:innodb有一个共享的表空间ibdata1,如果配置一下参数,可以使每张表都有一个表空间,用于存放数据、索引和插入缓冲的bitmap页,其他数据,如果回滚段、插入缓冲索引、系统事务、二次写缓冲依然在共享表空间中。
    段:表空间由段组成,常见的段有数据段,索引段,回滚段。由于innoDB的表示索引组织的,所以数据就是B+树所以的叶子节点,索引就是B+树的非叶子节点。
    区:区是由连续页组成的空间。每个区大小为1MB,为了保证区的连续性,innoDB一次从磁盘申请4-5个区。默认情况下innoDB存储引擎页的大小为16KB,即一个区由64个连续的页。innoDB段在初始分配的时候只有32个碎片页,只有在超过该大小后才连续分配。
    页:页是innoDB磁盘管理的最小单位。
    行:页中按照行存放数据,每个页最多存储7992行记录。

4.3 行格式
    在老的版本中使用称为 antelope(Compact, Redundent)的文件格式,在新版本开始使用barracuda(Compressed,Dynamic)的文件格式
    Compact行记录格式:变长字段长度列表(1-2字节),null标志位(1字节),记录头信息(5字节),事务ID列,回滚指针列,列数据。回滚指针列记录了undo log中的数据。页中所有行都通过链表串联起来。
    Redundent行记录格式:变长字段长度列表。记录头信息(6字节),列1,列2... 该格式是数据库5.0之前的版本格式。

    新的记录格式对于blob数据完全采用行溢出的方式。数据页中只存放20个字节的指针。Compressed格式会压缩blob页。

4.4 行溢出数据
    根据行格式定义可知,一行记录可以创建65535字节大小。但是数据页为16K,无法存储这么大的数据,当数据发生行溢出时候,数据存放在blob页中。而真正的数据页只保存了768字节的前缀数据,之后是偏移量,指向行溢出页。
    innoDB存储引擎表是按照B+树组织的,所以每页数据中至少包含2条记录。如果页中足够存放两条数据,则不会存放到blob页中。

4.5 页结构
    数据页可由7个部分组成。
    File Header。 文件头用来记录页的一些头信息。占用38个字节。checksum(4),page_offset(4)表空间中的页偏移,page_prev(4)上一个页,page_next(4)下一个页,page_lsn(8)最后被修改的日志序列。page_type(2)页类型。page_file_flush_lsn(8)文件至少被更新到该lsn值。space_id(4)记录该页输入那个表空间
    Page Header。 记录页的状态信息。占用56字节。page_n_dir_slots(2)页目录中的槽数量。page_heap_top(2)堆中第一个记录的指针。page_n_heap(2)堆中记录数。page_free(2)可重用空间的首指针。page_garbage(2)已删除的字节数,即delete flag为1的行记录总大小。page_last_insert(2)最后插入记录的位置。page_direction(2)最后的插入方向。page_n_direction(2)一个方向连续插入的记录数。page_n_recs(2)页中记录的数量。page_max_trx_id(8)修改当前页的最大事务ID.page_level(2)当前页在索引中的高度。page_index_id(8)当前页输入那个索引。page_btr_seg_leaf(10). page_btr_seg_top(10)段头。
    Infimun和Supremun。innoDB中,每个数据页有2个虚拟的行记录,用来限定记录的边界。指定该页中比主键最小的值和可能的最大主键值。
    User Record。实际行数据。
    Free Space。空闲空间,将空闲空间通过链表组织起来
    Page Directory。页目录中存放了记录的相对位置。该目录是一个稀疏目录,所以二叉查找为一个近似结果,因此Innodb需要通过行记录的nextRecord继续查找。
    File Trailer。为了检测页是否已经完整的写入磁盘。通过8字节的 page_end_lsn字段校验与file head中的checksum和lsn保证页的完整性. innoDB在从磁盘读取一个页就是检测页的完整性。
    
五、索引与算法
5.1 索引分类
    聚集索引:就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的即为整张表的行记录数据。聚集索引的这个特性决定了索引组织表中的数据也是索引的一部分,每个数据页都通过一个双向链表来进行链接。由于实际的数据页只能按照一颗B+树进行排序,所以每张表只能有一个聚集索引。通过聚集索引,查询优化器能很快找到某一页的数据,或者进行范围扫描。
    辅助索引:也称为非聚集索引,辅助索引的叶子节点除了包含键值以外,还包含了相应行数据的聚集索引件。查询通过辅助索引找到聚集索引键后,再通过聚集索引键查询数据。
5.2 B+树的分裂
    如果从节点的中间键进行分裂,由于插入时按照主键顺序进行的,将导致一半的页空间浪费。为了避免这种情况,innoDB在page head中加入几个状态字段指导分裂的位置。
    page_last_insert:最后插入键的位置
    page_direction: 最后插入的方向
    page_n_direction:最后连续插入n个记录的方向。
    B+树分裂规则:以当前插入记录的前一条记录为轴,如果轴后面有3条记录,则分裂点为该位置,否则分裂点为插入点本身。
5.3 索引管理
    cardinality:索引中唯一键的估计值。cardinality的值应该尽可能接近于1,这样表示其可选择性越高,建立索引的优势更大。否则,根据该索引查询依旧需要进行范围扫描。cardinality的值并不是实时更新的,如果需要更新改值需要使用analyze table命令。在非高峰期做该命令,可以让优化器和索引更好的工作。
    cardinality更新方式:1. 表中1/16的数据已经发生过变化 2.stat_modified_counter > 20亿。 当出现这两个条件的时候发生更新,通过采用统计8个页中不同的记录数/8 然后乘总页数来给出cardinality的预估值。
    旧索引创建:1 创建一张新的临时表(具有新的索引结构) 2. 把原表数据导入临时表 3 删除原表。 4 重命名原表
    快速索引创建:只针对辅助索进行重建,需要对表加S锁
    Online DDL:将DML操作日志写入到缓存中,待完成索引创建后再将重做应用到表上。,以此达到数据一致性的目的。
5.4 索引覆盖
    innoDB支持从辅助索引中就可以查询到记录。由于辅助索引不包含整行记录信息,估其大小原小于聚集索引。如果走辅助索引就能拿到查询的列数据或者进行统计计算,则优化器会先选择辅助索引。
5.5 索引提示
    通过索引提示来指示查询优化器强制使用某个索引,同时优化器不需要分析使用哪个索引。 
    use index(`a`): 告诉优化器使用哪个索引,但是不强制
    force index(`a`): 强制优化器使用某个索引
5.6 multi_range read优化
    MRR将对辅助索引的随机访问,转化为较为顺序的数据访问,适合于范围类型的查询,可以通过参数 optimizer_swith='mrr=on' read_rnd_buffer_size控制缓存区的大小
    1. 将查询得到的辅助索引键值存放在一个缓存中。
    2. 将缓存中的键值根据rowID进行排序
    3. 按照rowID排序的顺序来访问实际的数据文件
5.7 index condition pushdown (ICP)优化
    将查询条件推到索引查询的时候,取出索引的同时就进行where条件的过滤

六、锁
6.1 锁的类型
    共享锁S:允许事务读取一行数据,共享锁可以与共享锁兼容
    排它锁X:允许事务更改一行数据,排它锁不兼容任何锁。
    意向锁:innoDB的意向锁是一个表级锁,设计目的主要是为了在一个事务中揭示下一行将被请求的锁类型。
    意向共享锁:表示事务想要获得一张表中某几行的共享锁
    意向排他锁:表示事务想要获得表中某几行的排它锁
6.2 一致性读取方式
    一致性非锁定读-MVCC:多版本并发控制,事务读取数据无需访问锁,事务在读取数据的时候总是读取事务开始时候的最新版本。这个版本即为undo log
    一致性锁定读:读取的时候进行加锁
    select lock for update加x锁
    select lock in share mode加s锁 
    自增长与锁:每个表都拥有一个自增长计数器,在自增计数器的时候,如果插入的时候行数确定(simple insert),则需要通过mutex进行并发控制。如果函数不确定(insert select)则会上表锁。
6.3 锁的算法
    1. record lock 单个行记录锁,如果表没有添加索引,则锁住隐式主键
    2. gap lock 间隙锁,锁定一个范围,但不包括记录本身
    3. next-key lock:锁定一个范围且锁定记录本身。innoDB对于行的查询都是使用这种锁定算法。例如:对于一个索引有 10,11,13,20这四个值,则[-无穷,10],[10,11],[11,13],[13,20],[20,+无穷]这几个区间全部被锁定
    next-key lock降级,对于唯一索引,如果查询条件唯一,则可降级为行锁。这样对于某些查询可以提高性能。但是对于辅助索引则不可降级,这样确保走辅助索引查询时事务不会出现幻读。
    备注: innoDB在可重复读隔离级别下开启了,间隙锁。所以在该级别就可以避免幻读问题。而oracle数据库必须要序列化级别才能避免幻读。 总的来说事务的隔离级别,对于发生回滚和范围数据查询才具有意义。
6.4 锁升级
    锁升级:即由行锁升级为表锁或者页锁。由于innoDB以页的维度管理锁资源,并且通过位图的方式记录锁。所以不存在锁升级的问题,且锁维护的开销也很小。

七、事务
7.1 事务特性
    原子性:一组操作要么都执行成功,要么都不执行。
    一致性:将数据库从一种一致的状态变为下一种一致的状态。
    隔离性:事务之间是相互隔离。
    持久性:事务一旦提交,其结果就是永久性的
7.2 事务种类
    扁平事务:从开始执行事务到提交,或者回滚
    带有保存点的扁平事务:允许事务回滚到同一事务的较早的一个状态。保存点用来记录事务当前的状态
    链事务:一个事务提交后自动开启下一个事务,该类型事务回滚只能回滚到前一个保存点
    嵌套事务:顶层事务提交才出发子事务的提交。任一子事务回滚,将导致整个事务回滚
    分布式事务:通常是一个在分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点。
7.3 事务的实现
    redo log:是物理日志,记录数据页的修改记录。事务更新数据过程中,会在内存的重做日志缓冲中写redo log,事务提交的时候需要调用fsync将内存缓冲中的日志刷新到磁盘中。
    innodb_flush_log_at_trx_commit参数控制刷磁盘的方式:默认1 事务提交立马执行一次刷盘;2 事务提交将日志写入文件系统缓冲 0 只由master线程刷新磁盘
    log block:重做日志都是以512字节进行存储的,称之为重做日志块。由于重做日志块大小和磁盘扇区大小一样,因此日志的写入可以保证原子性。
    log block header:12字节。用来记录数据长度,起始偏移等信息
    log block tailer:8字节。 
    log body:492字节 
    重做日志文件中存储的就是log buffer中保存的log block,innodb根据一定的规则将log block中的内容刷新到磁盘。1 事务提交时候 2. log buffer中有一半的内存空间已被使用 3 log checkpoint
    备注:log group的第一个重做日志文件前2KB需要保持log file head和 checkpoint等信息,并经常更新,所以对重做日志文件的写入并不是完全顺序的。
    LSN:表示重做日志写入的总字节量,该值不仅记录在redo log中而且写在所有页头中,表示该页最后刷新时LSN的大小     
    恢复:由于redo log文件记录了checkpoint的信息,所以在恢复的时候仅须要恢复checkpoint开始的日志部分,innodb将顺序读取日志文件,并行应用重做日志
    undo log:undo log用于逻辑性的记录数据的更改记录(对原操作进行反向操作),在事务回滚的时候利用该日志恢复到最初的样子。 undo log存放在共享表空间的回滚段中,undo log的产生也会写redo log
    undo存储管理。事务在写undo log时候需要在回滚段中申请undo页,在事务提交时将undo log放入列表中(有purge线程来判断是否删除该页),供purge操作,并判断该页是否可以重用。
    insert undo log:插入操作产生的log,在事务提交后直接删除,无需purge
    update undo log:更新产生的log,事务提交后需要提交到链表中,供purge线程检查
    purge线程:history list按照事务提交的顺序将undo log进行组织,在innodb中由于undo log的重用,一个undo page可能有多个事务的日志。在执行purge过程中,innodb先从history list中找到第一个需要清理的记录。如果找的的页中有其他事务的记录,则嵌套去检查并清理该事务记录,在该页清理完成后,继续去读history list。innodb这样设计主要为了减少随机读取操作。
7.4 组提交
    事务在提交的时候需要进行两个过程:1 将日志写入重做日志缓冲redo log block 2 调用fsync进行刷盘。 组提交就是一次fsync可以将多个事务的日志刷新到磁盘中
    由于在主备模式下,事务提交需要同时刷二进制日志和重做日志,需要确保这两个过程是原子的。为了保证二进制日志的写入顺序,和引擎层的提交顺序一致。需要使用prepare_commit_mutex锁来使引擎层同步顺序提交事务。以免通过在线恢复的时候数据丢失。但是由于fsync需要同步,导致了组提交带来的性能优势损失
    在数据库5.6中将事务的提交过程分为几个步骤来完成
    1. flush阶段,将每个事务的二进制日志写入内存中
    2. sync阶段,将内存中的二进制日志刷新到磁盘
    3. commit阶段,leader根据顺序调用存储引擎层事务的提交。
    blgc事务提交方式:将提交的事务顺序放入一个队列中,队列中的第一个事务称为leader,其他事务为follower,leader控制follow的行为。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章