陆陆续续花了将近半个月把这本书看完了,全书一共分为10大章节,其中挑了自己觉得比较有意思的章节进行阅读,分别是
上面没列下来的章节,有些是涉及到文件存储、性能调优参数的,感觉并不是很重要,就简单的翻翻留个印象就略过了。
总体来说,读完这本书后,能对Innodb存储引擎有个大概的认识,能初略了解其实现原理。里面很多原理设计都是十分经典,很值得学习。
首先是第一章,简单介绍了Mysql的体系架构。
可以看出来,Mysql表存储引擎是做成插件化的,可选择性很多。与其他数据库相比,InnoDB有以下特点
先看一下InnoDB的简单体系图
从图中可以看到,InnoDB的体系简单分为三块,分别是后台线程,内存池,文件。
后台线程主要作用
后台线程分为
关于第二点IO Thread,会使用AIO呢?有两个好处
第一是因为一个SQL的查询,可能需要读取磁盘中的多个索引页,AIO可以并行读取数据,,提高读取的效率。
第二是可以进行IO合并操作,当读取的多个页是相邻的,可以合并为一个IO请求,提高IOPS。
// TODO AIO的实现由Linux内核支持,但是具体的实现原理,还有待后续探究。
我们都知道内存操作的速度是远远大于磁盘操作速度的。所以Innodb采用缓冲池来提高数据库的性能。
数据库读取页的时候,会先判断缓冲池中是否已经存在该页,如果存在,就直接使用缓冲池中的页,如果不存在,就从磁盘中进行读取。
对于页的修改,也是先修改缓冲池中的页,然后再以一定的频率刷新到磁盘进行持久化。
如果数据在刷回磁盘前数据库异常了怎么办?答案是根据Redo日志进行恢复。InnoDB会保证,在页修改时,Redo日志必须已经持久化到磁盘。
InnoDB使用LRU来管理缓冲中的页,朴素的LRU会把最新的数据插入到头部,淘汰掉尾部的数据。InnoDB使用的LRU有一点不同的是,最新的数据会插入到中间的位置,这是为了避免一些冷门的大SQL需要扫描很多页,导致热点数据被刷掉。
在上面页的修改中提到过,重做日志信息会先放到重做日志缓冲区,然后按照一定的频率刷回磁盘文件,通常情况下,这三种情况会触发刷新。
CheckPoint可以理解为检查点,每到检查点,缓冲池中的脏页数据会马上刷回到磁盘。
Master Thread上面简单提到过,会处理负责缓冲池数据刷回磁盘,脏页刷新,合并插入缓冲,UNDO页回收等。下面简单说一下它的功能。
每秒一次的
即使事务还没有提交,InnoDB仍然会每秒将重做日志缓冲内容刷新到重做日志文件,所以即使是很大的事务也能快速提交。
每十秒一次的
先解释一下插入缓冲,插入缓冲指的是非聚簇索引的插入方式是先缓冲到内存中,一段时间后再刷新到磁盘文件里面的。
插入缓冲需要满足两个条件
为什么要这么处理呢?我们知道InnoDB是用B+Tree的索引结构来存储数据的,我们一般会使用自增长的ID作为主键,因为其是自增的,所以在磁盘文件中,写入的数据地址在磁盘中都是顺序的,但是非聚簇索引在存储结构上并不是连续的,这时候需要离散地读取非聚簇索引页,造成插入的性能下降。
缓冲池中脏页在写入磁盘时,可能会受到宕机的影响导致脏页数据未完全写入磁盘,导致数据丢失。
为了解决这个问题,Innodb引入了double-write机制。
double-write的流程是,脏页数据会先写入double-write缓冲区中(doublewrite-buffer),然后doublewrite-buffer中数据会写入共享空间磁盘,写入成功后,才会真正写入到数据存储的磁盘中。
如果在写入数据存储磁盘时发生意外宕机,磁盘中数据只写入了一部分,mysql会从共享空间磁盘中直接将该页数据复制到数据存储磁盘。
关于doublewrite的性能影响其实并不大,原因是第一是脏页中一般会有很多数据同时刷新到磁盘,Innodb会将这些操作进行合并操作,减少fsync的操作次数。第二是写入共享空间时是顺序写入的。
InnoDB的自优化,InnoDB会监控表上索引页的查询,如果发现建立hash索引可以带来速度提升,就会建立哈希索引。
前提是确定的查询条件(where a=xxx),范围查询不适用(where a > xxx)。
这章简单略过,主要是介绍表的存储结构。主要看下图就可以了。InnoDB的数据结构都是用B+Tree来组织的。
粒度从左到右逐渐变细,需要注意的是页是InnoDB管理磁盘的最小单位,InnoDB从磁盘中读数据都是按页读的。
tablespace(表空间) -> segment(段) -> extend(区) -> page(页) -> row(行)
InnoDB支持三种索引,分别是B+树索引,全文索引,Hash索引。全文索引和Hash索引就不详细些了,主要写B+树索引。
上图是书中举例的一棵B+树,可以看下它有以下特点
然后,为什么会使用B+树来组织索引数据呢?
所以,为什么DBA老是跟我们说不要用大VARCHAR建立索引呢?用长的VARCHAR建立索引,页能存储的数据数目就变少了,效率自然就降低了。
聚簇索引就是按照表主键构建的B+Tree,叶子节点存储的就是表的行记录数据。
叶子节点不包含行的记录数据,而是存储聚餐索引的聚簇索引键,一般就是主键。
当通过非聚簇索引查找数据时,需要先在非聚簇索引树中找到对应的聚簇索引键,然后再根据键到聚簇索引树中查找数据。
所以也不要认为建一个非聚簇索引就万事大吉了,如果在非聚簇索引键上进行大范围的数据查询,还是得离散地访问聚簇索引树,效率不一定比全表扫描快。
略
略
Mysql5.5.6后支持的,目的是减少磁盘的随机访问,将随机访问转换为较为顺序的数据访问。
工作方式为
如果多个键值在同一个页里面,就能一次磁盘访问就把数据捞出来了。
这个也是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实现了两种标准的行级锁
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