mysql 索引结构

Innodb 架构

  1. Buffer Pool 用于加速读
  2. Change Buffer 用于没有非唯一索引的加速写
  3. Log Buffer 用于加速redo log写
  4. 自适应Hash索引主要用于加快查询页。在查询时,Innodb通过监视索引搜索的机制来判断当前查询是否能走Hash索引。比如LIKE运算符和% 通配符就不能走。

https://upload-images.jianshu.io/upload_images/24630328-701b5738c2cb5ce3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

缓冲池 buffer pool

  • 数据是存在磁盘的idb文件中,交互时速度必然是比较慢的。这时就需要将数据加载到内存中。
  • innodb逻辑上的最小单位是页(16k),那么每次加载的页到内存的区域(预读),就是buffer pool
  • 下一次取数据(数据页或者索引页)时,会优先判断在不在buffer页中。修改时,会修改内存中的数据,当buffer pool中数据和磁盘不一致时,这时就叫脏页。会有工作线程会定时同步,这个操作就叫刷脏

../../../../blogimgv2022/image-20220730231513666.png

  • mysql改进的LRU:
    • buffer pool 分为了young(5/8)和old(5/3)两个部分
    • 数据会在用户的sql用到了页中的数据,或者mysql猜测你很可能会用到的数据-预读,这两种情况加载到buffer pool
    • 数据优先进入old,old满了会移出队列尾部。young区呢? 用户sql使用的页,会被移入young(且需要在old中待满配置的秒数)。而预读的数据,没被使用则会一直在old区,直到被清除

问题: 当一次大表扫描,会导致大量数进入young?

解决:mysql防止一次扫描数据过大,替换了大量热数据,有参数控制 innodb_old_blocks_time(需要在old区待满多少秒,且再被用户sql调用时,才会进入young)。

../../../../blogimgv2022/image-20220730232330078.png

change Buffer

当数据不存在于buffer pool时,且更新操作的数据没有唯一索引(非唯一,非主键,不是降序索引),不存在重复数据的情况,可以直接在change buffer中记录操作。

原因: 这样的数据,不需要校验唯一性,直接更新即可,没必要加载数据到buffer pool,而且查询时,数据也是随机分布在不同的页上面,可能需要加载多个页的数据,产生大量IO操作。

change buffer的数据什么时候会同步出去:

  • 访问这个数据页时、执行merge操作,将修改操作合并,后面同步到磁盘
  • 空闲时后台线程处理
  • shoutdown

Log Buffer

官方描述:日志缓冲区是保存要写入磁盘上日志文件的数据的内存区域。日志缓冲区大小由 innodb_log_buffer_size变量定义。默认大小为 16MB。日志缓冲区的内容会定期刷新到磁盘。大的日志缓冲区使大事务能够运行,而无需在事务提交之前将redo log 数据写入磁盘。因此,如果您有更新、插入或删除许多行的事务,则增加日志缓冲区的大小可以节省磁盘 I/O。

用来记录操作日志,先写入到log buffer ,然后事务提交,或者缓冲区满了的时候写入磁盘文件 redo log

redo log

  • 是一种基于磁盘的数据结构,用于在崩溃恢复期间纠正由不完整事务写入的数据

  • 作用: 主要用来做崩溃恢复,用来实现事务持久性

  • 内部机制:数据存在缓冲区,没有进行刷盘操作时,如果数据库宕机或者重启,会导致数据丢失。 redo log的作用就是,数据写入缓存后,写一下redo log ,记录的不是最终的结果,而是要进行的操作。崩溃恢复时,将redo log数据重新加载到缓冲区。

  • 策略: 0(延时写) 1 2(实时写,延时刷) 默认是1 事务操作实时记录并刷新数据页

疑问:写到日志文件还是一次磁盘io,为什么不直接写到数据页 ?

  • 顺序IO和随机IO 写日志是顺序io,效率高(可以参考Kakfa)
  • 记录的是操作,不是结果,数据量其实不大

Adaptive Hash Index

osCache

磁盘中主要组成部分是5类表空间

  • **系统表空间(ibdata1):**组成:数据字典(表和索引的元信息) 、双写缓冲区 、undo log、Change buffer

    • 双写缓冲: Innodb从buffer pool flushed数据页过程中,如果出现崩溃等异常情况(简单理解就是,刷数据时出现刷一半崩溃的情况,内存被清空了,磁盘也刷了一半数据,这种场景需要处理), innodb 为我们提供双写机制,先拷贝一份数据的副本,如果崩溃,就将副本恢复。虽然数据需要双写,但是并不是两倍的IO开销,因为写入缓冲区是一次 large sequential chunk (官网这一句理解不了,暂时理解为顺序写入开销不大)
  • 独占表空间: 可以为每个表设置为独占表空间

  • 临时表空间:

  • 通用表空间:

redo log:记录数据到磁盘,用于崩溃恢复,保证事务的持久性

undo log:撤销回滚日志 记录事务发生之前的状态,如果发生异常,可以使用undo log回滚,存储的是旧数据,并发事务时使用回滚段来控制。

bin log ,server层的日志。主要是用来主从复制(原理就是读取主库的bin log,执行一遍sql),数据恢复。记录所有的sql语句,有点aof的味道,可以将操作重复实现数据恢复。和redo log不一样,他的内容可以追加,没有大小限制

Mysql 内部模块

../../../../blogimgv2022/image-20220730231259644.png

update sql 流程

  1. 查询到我们要修改的那条数据,我们这里称做 origin,返给执行器
  2. 在执行器中修改数据,称为 modification
  3. 将modification刷入内存,Buffer Pool的 Change Buffer
  4. 引擎层:记录undo log (实现事务原子性)
  5. 引擎层:记录redo log (崩溃恢复使用)
  6. 服务层:记录bin log(记录DDL)
  7. 返回更新成功结果
  8. 数据等待被工作线程刷入磁盘

../../../../blogimgv2022/image-20220730233615993.png

../../../../blogimgv2022/image-20220730233723652.png

主从复制方案

  1. 全同步复制,事务方式执行,主节点先写入,然后让所有slave写,必须要所有 从节点 把数据写完,才返回写成功,这样的话会大大影响写入的性能
  2. 半同步复制,只要有一个salve写入数据,就算成功。(如果需要半同步复制,主从节点都需要安装semisync_mater.so和 semisync_slave.so插件)
  3. GTID(global transaction identities)复制,主库并行复制的时候,从库也并行复制,解决主从同步复制延迟,实现自动的failover动作,即主节点挂掉,选举从节点后,能快速自动避免数据丢失。

数据库索引

  • 索引记录的是索引字段信息+对应的磁盘地址,以便于快速查找

../../../../blogimgv2022/image-20220730233844301.png

innodb的索引:聚集索引 非聚集索引(又叫二级索引,普通索引)

  • 有主键,主键会作为聚集索引
  • 无主键,第一个非空的唯一索引作为聚集索引
  • 没有合适的唯一索引,默认创建row id 来作为聚集索引
  • 聚集索引的作用: 优化查询、插入和其他数据库操作的性能 聚集索引直接指向数据对应的页,所以节省io

二级索引和聚集索引的关系:二级索引中其实包含主键信息,通过主键值到聚集索引中找到对应的行数据。所以PK其实越小越好

../../../../blogimgv2022/image-20220730234019815.png

在MYISAM内部,其实B+Tree中维护的叶子节点,也全部都是数据文件的地址信息,根据MYI中的index的地址,然后查询.MYD。主键索引和辅助索引都是这种形式

0%