《Mysql技术内幕-Innodb存储引擎》读书笔记

引子

陆陆续续花了将近半个月把这本书看完了,全书一共分为10大章节,其中挑了自己觉得比较有意思的章节进行阅读,分别是

  1. 第一章 Mysql体系结构和存储引擎
  2. 第二章 InnoDB存储引擎
  3. 第五章 索引与算法
  4. 第六章 锁

上面没列下来的章节,有些是涉及到文件存储、性能调优参数的,感觉并不是很重要,就简单的翻翻留个印象就略过了。

总体来说,读完这本书后,能对Innodb存储引擎有个大概的认识,能初略了解其实现原理。里面很多原理设计都是十分经典,很值得学习。

Mysql体系结构和存储引擎

首先是第一章,简单介绍了Mysql的体系架构。

体系结构

可以看出来,Mysql表存储引擎是做成插件化的,可选择性很多。与其他数据库相比,InnoDB有以下特点

  1. 支持事务
  2. 支持行级锁
  3. 支持非锁定读(默认读取操作不会产生锁)

InnoDB存储引擎

先看一下InnoDB的简单体系图

InnoDB体系图

从图中可以看到,InnoDB的体系简单分为三块,分别是后台线程,内存池,文件。

后台线程

后台线程主要作用

  1. 是刷新内存池中的数据,保证缓冲池中数据都是最新的数据。
  2. 将缓冲池中的数据刷刷新到磁盘文件中.
  3. 保证发生异常时候,发生缓冲池和磁盘数据不一致时能正常恢复。

后台线程分为

  1. Master Thread - 核心线程,负责缓冲池数据刷回磁盘,脏页刷新,合并插入缓冲,UNDO页回收等。
  2. IO Thread - 负责AIO的回调处理
  3. Purge Thread - 回收已经没有用的UNDO页(从Master Thread抽离)
  4. Page Cleaner Thread - 脏页刷新回磁盘(从Master Thread抽离)
IO Thread

关于第二点IO Thread,会使用AIO呢?有两个好处

  1. 第一是因为一个SQL的查询,可能需要读取磁盘中的多个索引页,AIO可以并行读取数据,,提高读取的效率。

  2. 第二是可以进行IO合并操作,当读取的多个页是相邻的,可以合并为一个IO请求,提高IOPS。

// TODO AIO的实现由Linux内核支持,但是具体的实现原理,还有待后续探究。

缓冲池

我们都知道内存操作的速度是远远大于磁盘操作速度的。所以Innodb采用缓冲池来提高数据库的性能。

InnoDB内存数据对象

页的读取

数据库读取页的时候,会先判断缓冲池中是否已经存在该页,如果存在,就直接使用缓冲池中的页,如果不存在,就从磁盘中进行读取。

页的修改

对于页的修改,也是先修改缓冲池中的页,然后再以一定的频率刷新到磁盘进行持久化。

如果数据在刷回磁盘前数据库异常了怎么办?答案是根据Redo日志进行恢复。InnoDB会保证,在页修改时,Redo日志必须已经持久化到磁盘。

页的管理

InnoDB使用LRU来管理缓冲中的页,朴素的LRU会把最新的数据插入到头部,淘汰掉尾部的数据。InnoDB使用的LRU有一点不同的是,最新的数据会插入到中间的位置,这是为了避免一些冷门的大SQL需要扫描很多页,导致热点数据被刷掉。

重做日志缓冲

在上面页的修改中提到过,重做日志信息会先放到重做日志缓冲区,然后按照一定的频率刷回磁盘文件,通常情况下,这三种情况会触发刷新。

  1. Master Thread每一秒将重做日志刷新
  2. 事物提交
  3. 重做日志缓冲池空间少于1/2(默认为8m)

Check Point(检查点)

CheckPoint可以理解为检查点,每到检查点,缓冲池中的脏页数据会马上刷回到磁盘。

  1. Master Thread Checkpoint - 每秒或十秒一次
  2. FLUSH_LRU_LIST Checkpoint - LRU空间页不足时
  3. Async/Sync Flush Checkpoint - 重做日志不可用时

Master Thread工作方式

Master Thread上面简单提到过,会处理负责缓冲池数据刷回磁盘,脏页刷新,合并插入缓冲,UNDO页回收等。下面简单说一下它的功能。

每秒一次的

  1. 日志缓冲刷新到磁盘
  2. 合并插入缓冲
  3. 刷新100个InnoDB缓冲池中的脏页到磁盘

即使事务还没有提交,InnoDB仍然会每秒将重做日志缓冲内容刷新到重做日志文件,所以即使是很大的事务也能快速提交。

每十秒一次的

  1. 刷新100个脏页到磁盘
  2. 合并至多5个插入缓冲
  3. 将日志缓冲刷新到磁盘
  4. 删除无用的Undo页

插入缓冲

先解释一下插入缓冲,插入缓冲指的是非聚簇索引的插入方式是先缓冲到内存中,一段时间后再刷新到磁盘文件里面的。

插入缓冲需要满足两个条件

  1. 非唯一,因为唯一在插入时需要判断约束,无法缓冲
  2. 索引是非聚簇索引

为什么要这么处理呢?我们知道InnoDB是用B+Tree的索引结构来存储数据的,我们一般会使用自增长的ID作为主键,因为其是自增的,所以在磁盘文件中,写入的数据地址在磁盘中都是顺序的,但是非聚簇索引在存储结构上并不是连续的,这时候需要离散地读取非聚簇索引页,造成插入的性能下降。

两次写(Double Write)

缓冲池中脏页在写入磁盘时,可能会受到宕机的影响导致脏页数据未完全写入磁盘,导致数据丢失。

为了解决这个问题,Innodb引入了double-write机制。

double-write的流程是,脏页数据会先写入double-write缓冲区中(doublewrite-buffer),然后doublewrite-buffer中数据会写入共享空间磁盘,写入成功后,才会真正写入到数据存储的磁盘中。

如果在写入数据存储磁盘时发生意外宕机,磁盘中数据只写入了一部分,mysql会从共享空间磁盘中直接将该页数据复制到数据存储磁盘。

关于doublewrite的性能影响其实并不大,原因是第一是脏页中一般会有很多数据同时刷新到磁盘,Innodb会将这些操作进行合并操作,减少fsync的操作次数。第二是写入共享空间时是顺序写入的。

自适应Hash索引

InnoDB的自优化,InnoDB会监控表上索引页的查询,如果发现建立hash索引可以带来速度提升,就会建立哈希索引。

前提是确定的查询条件(where a=xxx),范围查询不适用(where a > xxx)。

这章简单略过,主要是介绍表的存储结构。主要看下图就可以了。InnoDB的数据结构都是用B+Tree来组织的。

粒度从左到右逐渐变细,需要注意的是页是InnoDB管理磁盘的最小单位,InnoDB从磁盘中读数据都是按页读的。

tablespace(表空间) -> segment(段) -> extend(区) -> page(页) -> row(行)

InnoDB逻辑存储结构

索引与算法

InnoDB支持三种索引,分别是B+树索引,全文索引,Hash索引。全文索引和Hash索引就不详细些了,主要写B+树索引。

B+树

B+树示例

上图是书中举例的一棵B+树,可以看下它有以下特点

  1. 所有数据都在叶子节点,非叶子节点不存储数据
  2. 叶子节点使用双向链表链接

然后,为什么会使用B+树来组织索引数据呢?

  1. 非叶子节点也是由页组成,每个页的大小是有限的,而B+树非叶子节点不存储真实数据,使得非叶子节点可以放入非常多的索引数据,降低树的层数。而树的层数越低,扫描索引数据需要的次数也就越低。
  2. 数据都放在叶子节点中,每次索引扫描,需要进行IO磁盘次数都是一致的,所以每次扫描的时间都十分稳定。
  3. 叶子节点使用双向链表链接,可以很方便地进行范围查询。

所以,为什么DBA老是跟我们说不要用大VARCHAR建立索引呢?用长的VARCHAR建立索引,页能存储的数据数目就变少了,效率自然就降低了。

聚簇索引

聚簇索引就是按照表主键构建的B+Tree,叶子节点存储的就是表的行记录数据。

非聚簇索引

叶子节点不包含行的记录数据,而是存储聚餐索引的聚簇索引键,一般就是主键。

当通过非聚簇索引查找数据时,需要先在非聚簇索引树中找到对应的聚簇索引键,然后再根据键到聚簇索引树中查找数据。

所以也不要认为建一个非聚簇索引就万事大吉了,如果在非聚簇索引键上进行大范围的数据查询,还是得离散地访问聚簇索引树,效率不一定比全表扫描快。

联合索引

覆盖索引

MRR技术(Multi-Range Read)

Mysql5.5.6后支持的,目的是减少磁盘的随机访问,将随机访问转换为较为顺序的数据访问。

工作方式为

  1. 将查询得到的非聚簇索引键值存储到一个缓存中
  2. 缓存中的键值进行排序,再根据排序的顺序进行数据访问

如果多个键值在同一个页里面,就能一次磁盘访问就把数据捞出来了。

ICP优化(Index Condition Pushdown)

这个也是Mysql5.6后支持的。举一个例子进行了解

# index为product_id
# id为主键
select * from order where product_id = 'xxx' and order_detail like '%xxx%' and create_time like '%xxxx-xx-xx%'

如果没有开启ICP优化,数据库会先通过product_id找出所有的数据,然后再过滤where后的条件。
若支持了ICP优化,则会在索引取出时候,就会进行where条件的过滤。

// 其实感觉这个优化对我们目前来说有点鸡肋,书中举的例子,得需要主键是联合索引才行,比如上面的例子,如果主键为(id, order_detail),那数据库通过product_id获取到聚簇索引主键时候,也就能同时得到order_detail,这时候就能根据where条件去掉不符合条件的order_detail数据,减少符合的索引键数目,效率自然就提升了,但是平时我们建表的时候主键都不会这么建。

标准锁

InnoDB实现了两种标准的行级锁

  1. 共享锁(S Lock),允许事务读一行数据
  2. 排它锁(X Lock),允许事务删除或更新一条数据

S锁与S锁是兼容的,意思是当一条记录被S锁锁住的时候,可以继续往上面加S锁,但是S锁和X锁是互斥的,两者同时只能存在一个锁锁住记录。

# 共享锁写法
select * from table where a=1 in share mode

# 排它锁写法
select * from table where a=1 for update

除了这两种锁,InnoDB还有意向锁,分别为意向共享锁(IS Lock)和意向排它锁(IX Lock),这个锁的作用是,当事务要对细粒度的记录加锁时,会先从粗粒度开始往下加意向锁,如果碰到锁不兼容的情况就进行等待。通过意向锁,InnoDB支持了多粒度锁定(即表锁和行锁可以共存)

一致性非锁定读

简单来说,就是Mysql的普通查询语句,是不需要加锁的。如果查询中的数据刚好被锁了,这时候查询语句会查记录的快照版本,从而不需要等待行锁释放就能查询到记录。

转载于:https://www.cnblogs.com/miguel/p/11192396.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值