MySQL-InnoDB拾遗

之前写过一篇介绍MySQL中存储引擎的文章MySQL之存储引擎,在实际工作中,还是以InnoDB存储引擎为主,此文大部分为InnoDB引擎中的概念拾遗。

体系模型

InnoDB存储引擎体系结构

以上为InnoDB存储引擎大致上的体系结构。

其中后台线程负责:刷新内存池中数据;将已修改的数据文件刷新到磁盘。
后台线程又分为Master Thread与IO Thread。Master Thread负责将缓冲池中的数据异步刷新到磁盘,保证数据一致性,包括脏页的刷新,合并插入缓冲等。IO Thread则负责IO请求。

接下来是最核心的部分,内存池负责:缓存磁盘上的数据;在对磁盘文件的数据修改之前在这里缓存;重做日志(redo log)缓冲。内存池的结构又可划分如下图

内存池结构

内存池中最重要的结构显而易见是缓冲池。

缓冲池:MySQL数据基于磁盘存储,并将其中的记录按照页的方式进行管理。缓冲池是一段内存区域,来弥补磁盘速度较慢对性能的影响。对于读操作,会将磁盘读取到的页放在缓冲池中,对于写,首先修改在缓冲池中的页,再定时刷新到磁盘。缓冲池的数据淘汰通过LRU算法进行管理。插入缓冲:对于非唯一辅助索引的插入或更新,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若在直接插入,若不在,先放入insert buffer中,之后多次操作合并,提高性能。关于缓冲池更详细的介绍可以看58沈剑的这篇文章缓冲池,这次彻底懂了!

日志文件

几个重要的日志文件:二进制日志(MySQL层面),慢查询日志(MySQL层面),查询日志(MySQL层面),RedoLog(InnoDB层面),UndoLog(InnoDB层面)。查询日志包括慢查询日志就是SQL的执行记录,一般通过它们来进行SQL优化,具体的不做介绍,着重关注不暴露给外面的几种日志。

BinLog:binlog是MySQL层面的日志,记录了数据的更改,用于故障恢复,主从复制等。binlog共有三种模式:STATEMENT,记录SQL语句;ROW,记录行更改;MIXED,默认采用STATEMENT,某些特殊情况下采用ROW,例如使用了临时表。MySQL中有两个跟binlog相关的重要参数
(1)binlog_cache_size,二进制日志缓冲,当事务的记录大于设定的binlog_cache_size时,mysql会把缓冲区中的日志信息写入一个临时文件中。设置过大,会造成内存浪费。设置过小,会频繁将缓冲日志写入临时文件。
(2)sync_binlog,=0(默认)表示刷新binlog时间点由操作系统自身来决定,操作系统自身会每隔一段时间就会刷新缓存数据到磁盘,性能最好,但有可能丢失数据。=N,代表每N个事务提交会进行一次binlog刷新。N=1最安全,但性能较差。

RedoLog:InnoDB引擎层面,记录了InnoDB的事务日志,由两部分组成,一是内存中的RedoLog缓冲,二是RedoLog文件。RedoLog记录某数据块被修改后的值,可以用来恢复未写入data file的已成功事务更新的数据,保证事务的原子性和持久性。redo日志与binlog日志作用类似,但是前者为InnoDB引擎特有,后者为MySQL层面,所有的存储引擎都会产生binlog。前者是物理格式,记录的是每个页的修改;后者是逻辑日志,记录的是对应的SQL语句。binlog只在事务提交完成后进行一次写入,redolog则在事务进行中不断写入。同binlog一样,redolog也有相关刷新内存缓冲的参数,innodb_flush_log_at_trx_commit控制重做日志刷新到磁盘的策略,默认为1,表示事务提交时进行一次fsync,为0时表示每秒进行一次fsync(效率比前者高,但可能丢失一秒钟的数据)

UndoLog:InnoDB引擎层面,记录数据被修改前的值,可以用来在事务失败时进行rollback,保证事务的一致性,回滚行记录到某个特定版本,通常是逻辑日志,根据每行记录进行记录。

 binlog 和 redolog的一致性问题:MySQL使用内部XA(两阶段提交)解决了 binlog 和 redo log的一致性问题。MySQL中的XA实现分为:外部XA和内部XA;前者是指我们通常意义上的分布式事务实现(因为性能原因,生产分布式事务不会采用此方案);后者是指单台MySQL服务器中,Server层作为TM(事务协调者),而服务器中的多个数据库实例作为RM,而进行的跨库事务,也就是一个事务涉及到同一条MySQL服务器中的两个innodb数据库。同时内部XA也用来解决这两种日志的一致性问题。

B+树索引

InnoDB采用B+树作为底层的索引结构,B+树由二叉查找树(左子树键值小于根,右大于根),再由平衡二叉树(子树高度差最大位1),B树演化而来,专为磁盘等存储设备设计。B+树中所有记录节点都是按键值得大小顺序存放在同一层的叶子节点上,由各叶子节点指针进行连接。

一颗高度为2的B+树

B+树索引分为聚集索引和辅助索引,前者按照主键构造B+树(当InnoDB表没有主键时,会选择使用唯一非空索引或者自动创建的6字节指针作为主键。),同时叶子节点中存放的即为整张表的行记录数据,也将聚集索引的叶子节点称为数据叶,这个特性决定了索引组织表中数据也是索引的一部分。后者叶子节点中不包含行记录的全部数据,叶子节点除了包含键值以外,索引行中还包含了一个书签,该书签(其实就是相应行数据的聚集索引键)用来告诉InnoDB哪里可以找到与索引相对应的行数据。通过辅助索引获取数据的流程是先找到对应书签,再通过主键索引找到相应的数据

多个键值的B+树

数据按照(a,b)的顺序进行了存放,对于where a=xxx and b=xxx与where a=xxx都是可以使用(a,b)联合索引的。而对于where b=xxx则不能使用该索引,叶子节点b值也非有序,不能使用索引。这也是索引的左前缀原则的原理。

InnoDB中的锁

InnoDB中锁可以分为2个类型:
1.共享锁 :允许事务读一行数据
2.排它锁:允许事务删除或更新一行数据
锁的兼容性:若事务T1获得行R的共享锁,T2可以立即获取R的共享锁,这种情况称为锁兼容。排它锁与任何类型的锁都不兼容。

意向锁:InnoDB支持多粒度锁定,允许事务在行锁和表锁同时存在,为了支持不同粒度上的加锁操作,InnoDB使用了意向锁的概念。意向锁将锁定的对象分为多个层次,意味着事务希望在更细粒度上进行加锁。意向锁是表级锁,并且不会与行级的共享 / 排他锁互斥。比如要在表A的记录a上加行级排它锁,他要先获取表的意向排它锁,这时当其他事务想要获取表锁时就不用一行行判断行锁,直接获取失败。

一致性非锁定读:指InnoDB通过行多版本并发控制(MVCC)的方式来读取当前数据库中的数据。如果读取的行正在执行update或delete操作,此时的读取操作并不会等待行锁释放,而是读取行的一个快照数据。MVCC只在可重复读与读已提交下工作。对于前者,总是读取事务开始时的行数据版本,对于后者,总是读取被锁定行的最新一份快照数据。MVCC可以使得大部分读操作都不用加锁。
一致性锁定读:显式的对数据库读取操作进行加锁保证数据逻辑的一致性。对于select语句,支持两种模式:select...for update; select...lock in share mode。前者对读取的行记录加一个排它锁,其他事务不能对该行加任何锁。后者对读取的行记录加一个共享锁,其他事务可以加共享锁但不能加排它锁。这种方式也是我们熟知的数据库悲观锁。

行锁的3种算法:Record Lock:单个行记录的锁;Gap Lock:间隙锁,锁定一个范围,但不包含记录本身;Next-Key Lock:锁定一个范围,并包含记录本身。在可重复读隔离级别下,InnoDB使用Next-Key Lock与GAP Lock解决幻读问题。

数据的复制

上面说过,MySQL通过binlog完成数据的复制。当数据库做主从架构时,主库把数据更改记录到二进制日志中,备库将主库的日志复制到自己的中继日志(Relay log),备库读取中继日志的信息重放。MySQL会按照事务的提交顺序而非执行顺序来记录binlog。备库启动一个IO线程跟主库建立连接,然后在主库上启动一个二进制转储线程,该线程用于读取主库上二进制日志中的事件,如果该线程追赶上了主库,则会进入休眠状态。备库的SQL线程从中继日志中读取事件并执行。若binlog为statement模式,有些SQL可能略有问题,比如使用NOW函数,会有短暂延迟,若为row模式,无法处理主库修改表schema的情况。

主从同步延时的解决:半同步复制(当提交事务时,客户端接收到查询结束反馈前必须保证二进制日志已经传输到至少一台备库上),并行复制(从库开启多个sql线程,并行重放),直连主库(要求读写均走主库),修改代码逻辑(避免更新或插入后立即读取),在事务中进行操作(在事务中读写都走主库)

DDL的几种方式

1. Mysql在线DDL:创建索引等直接online ddl,5.6以上添加普通列、索引等同时支持读写,增加列要拷贝表,需要保证有足够空间。

2. 使用pt-online-shema-change:创建新表,alert新表,原表创建插入,更新,删除触发器(或者读取binglog日志),原表拷贝数据到新表,且触发器也开始映射到新表,重命名新表和原表。

3. 或者在slave上ddl,然后切换主从(谨慎用)

B树索引与B+树区别

B树结构

每个节点中不仅包含数据的key值,还有data值。

B+树结构

数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,非叶子节点只存储键值信息,相比于B树,一个磁盘块可以存放更多的信息,则可以减少磁盘访问次数。所有叶子节点之间都有一个链指针。数据记录都存放在叶子节点中。

数据都在叶子节点上,并且增加了顺序访问指针,每个叶子节点都指向相邻的叶子节点的地址。相比B-Tree来说,进行范围查找时只需要查找两个节点,进行遍历即可,提高了区间访问性能

数据库中的B+Tree索引可以分为聚集索引(clustered index)和辅助索引(secondary index)。上面的B+Tree示例图在数据库中的实现即为聚集索引,聚集索引的B+Tree中的叶子节点存放的是整张表的行记录数据。辅助索引与聚集索引的区别在于辅助索引的叶子节点并不包含行记录的全部数据,而是存储相应行数据的聚集索引键,即主键。当通过辅助索引来查询数据时,InnoDB存储引擎会遍历辅助索引找到主键,然后再通过主键在聚集索引中找到完整的行记录数据。

数据库索引采用B+树而不是B树的主要原因:B+树只要遍历叶子节点就可以实现整棵树的遍历,而且在数据库中基于范围的查询是非常频繁的,而B树只能中序遍历所有节点,效率太低。

问:为什么索引结构默认使用B-Tree,而不是hash,二叉树,红黑树?
hash:虽然可以快速定位,但是没有顺序,IO复杂度高。
二叉树:树的高度不均匀,不能自平衡,查找效率跟数据有关(树的高度),并且IO代价高。
红黑树:树的高度随着数据量增加而增加,IO代价高。

InnoDB使用聚集索引,数据记录本身被存于主索引的叶子节点上,这就要求同一个叶子节点内的各条数据记录按主键顺序存放,因此每当一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,如果页面达到装载因子,则开辟一个新的页(节点)如果表使用自增主键,那么每次插入新的记录时,记录就会顺序添加到当前索引节点后续位置,当一页写满,就会自动开辟一个新的页。这样就就会形成一个紧凑的索引结构,近似顺序填满,由于每次插入时也不需要移动所有数据,因此效率很高,也不会增加很多额外的开销维护索引。

如果使用非自增主键,由于每次插入主键的值近乎于随机,因此每次新纪录都要被插到现有索引页的中间某个位置,此时MySQL不得不为了将新纪录插到合适位置而移动数据,甚至目标页面可能已经被写到磁盘而从缓存中清除,这增加了很多额外开销,同时频繁的移动,分页造成了大量的碎片,得到不够紧凑的索引结构,后续不得不通过OPTIMIZE TABLE来重建并优化填充页面。

由于MySQL从磁盘读取数据时一块一块来读取的,同时,根据局部性原理,MySQL引擎会选择预读一部分和你当前读数据所在内存相邻的数据块,这个时候这些相邻数据块的数据已经存在于内存中。由于数据库大部分是查询操作,这个时候,如果主键是自增的话,数据存储都是紧凑地存储在一起的,那么对于局部性原理利用和避免过多地I/O操作都有着巨大的促进作用

页是MySQL管理存储空间的基本单位,页的本质就是一块16KB大小的存储空间,InnoDB为了不同的目的而把页分为不同的类型,其中用于存放记录的页也称为数据页,如果记录占用的空间太大还可能造成行溢出现象,这会导致一条记录被分散存储在多个页中

MySQL中删除记录不会立即从磁盘上移除,是因为移除它们之后把其他的记录在磁盘上重新排列需要性能消耗,所以只是打个删除标记而已,而且这部分存储空间之后还可以重用,也就是说之后如果有新记录插入到表中的话,可能把这些被删除的记录占用的存储空间覆盖掉。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280

推荐阅读更多精彩内容