当前位置: 移动技术网 > IT编程>数据库>Mysql > MySQL技术内幕:InnoDB存储引擎读书笔记

MySQL技术内幕:InnoDB存储引擎读书笔记

2020年07月23日  | 移动技术网IT编程  | 我要评论

引言

本书介绍InnoDB的体系结构和工作原理,并结合InnoDB的源代码讲解了它的内部实现机制。
why比what重要
通过阅读本书,你将理解InnoDB存储引擎是如何工作的,它的关键特性的功能和作用是什么,以及如何正确地配置和使用这些特性。

第一章 介绍MySQL体系结构和对比存储引擎

实例是一个mysql server进程
数据库是一个物理操作系统文件

MySQL的体系结构
在这里插入图片描述

  • 连接池组件
  • SQL接口组件
  • 查询分析器组件
  • 优化器组件
  • 缓冲组件
  • 插件式存储引擎
  • 物理文件

Innodb
Innodb是表存储引擎,支持事务,面向在线事务处理,特点是行锁设计,支持外键,没锁定读(MVCC)。
next-key lock避免幻读、插入缓冲、二次写、自适应哈希索引、预读等高性能和高可用
对于表的存储采用聚集(按主键的大小顺序排放)的方式存储数据,使用非聚集的方式存放索引。

MyISAM
表锁,无事务,全文索引 面向OLAP

其他存储引擎忽略先不看。

第二章 InnoDB存储引擎

完整支持ACID事务、行锁设计、支持MVCC、支持外键

InnoDB体系架构
InnoDB有多个内存块,可以认为这些内存块组成了一个大的内存池。

  • 维护所有进程/线程需要访问的多个内部数据结构
  • 缓存磁盘上的数据,对磁盘文件的数据修改之前在这里缓存
  • 重做日志(redo log)缓冲。记录的是物理页的存放内容

在这里插入图片描述
后台线程的主要作用是负责刷新内存池中的数据,保证缓冲池中内存缓存的是最近的数据。把已修改的数据文件刷到磁盘文件,保证数据库发送异常情况下InnoDB恢复到正常状态。

线程
InnoDB的后台线程有master thread、IO thread、锁监控线程、错误监控线程。IO线程数由innodb_file_io_threads控制
(在master线程几乎实现所有的功能)
IO线程的类型有:insert buffer thread、log thread、read thread、write thread

master thread
该线程主要执行几个循环:主循环(loop)、后台循环(background loop)、刷新循环(flush loop)、暂停循环(suspend loop),master线程根据数据库的允许状态在这些循环中切换。
主循环伪代码
每秒一次的操作:

  • 日志缓冲刷新到磁盘,即使这个事务还没有提交(再大的事务commit也很快)
  • 合并插入缓冲(根据IO决定)
  • 刷新最多100个缓冲池的脏页到磁盘(根据脏页比例来判断)
  • 没有用户活动,切换到background loop

每10秒的操作:

  • 刷新脏页
  • 合并插入缓冲
  • 刷新重做日志
  • 删除无用的undo日志
  • 产生一个检查点:把脏页刷新到文件,这有恢复的时候执行的redo log就少了。

后台循环主要也是在做刷新日志文件,删除无用的undo,合并插入缓存

purge thread
用来回收使用并分配的undo页。

page cleaner thread
把脏页的刷新放到了单独的线程

IO thread
使用AIO来处理写IO请求,提供数据库的性能。IO thread是负责这些IO 请求的的回调

内存
InnoDB存储引擎内存由以下几部分组成:

  • 缓冲池:占最大块内存的部分,用来存放各种数据的缓存。InnoDB的存储引擎的工作方式总是将数据库文件按页读取到缓冲池,LRU淘汰数据。数据需要修改时,先修改缓冲池的页,修改后变成脏页,再按刷盘机制把脏页刷新到文件
  • 重做日志缓冲池
  • 额外的内存池:管理缓冲池的对象内存从此处申请

缓冲池
缓冲池中缓冲的数据页:索引页,数据页,undo页,插入缓冲,自适应哈希索引,InnoDB存储的锁信息,数据字典信息。
通过checkpoint把脏页刷新到文件
lru list:控制缓存的淘汰
free list:空闲缓存
flush list:需要刷新的list

重做日志缓存区
保证每秒的事务log小于8m

额外的内存池
额外的内存池:管理缓冲池的对象内存从此处申请

checkpoint
把脏页刷到文件。降低redo log的恢复时间。回收重做日志。

Innodb内存结构
在这里插入图片描述
InnoDB的关键特性

  • 插入缓冲
  • 自适应hash索引
  • 异步IO
  • 两次写
  • 刷新领接页

插入缓冲
对于非唯一非聚集索引的插入和更新,是创建或者更新插入缓冲中的insert buffer对象。再按一定频率刷新到文件

change buffer
与插入缓冲类似,提供了delete buffer和purge buffer

两次写
把页写入磁盘并不是原子性的,当写到一半时宕机。这种情况称为部分写失效。
先写到临时页,保证有一个待写页的副本。
在这里插入图片描述

自适应哈希索引
根据访问频率对b+树的某些热点页建立hash索引。

异步IO
提高IO性能,减少线程等待。

刷新领接表
查找页所在区的所有页是否需要刷新,一起刷新可用利用AIO提高性能。

第三章 文件

表结构定义文件
文本文件,每个表都有一个.frm文件。

重做日志文件
在这里插入图片描述

第四章 表

逻辑存储结构:数据存放在表空间。由段 区 页组成

常见的段有数据段、索引段、回滚段

默认情况下所有表的数据都存放在共享表空间下。当开启了innodb_file_per_table,那么每张表的部分类型的数据会存放到每张表的表空间。
每张表的表空间存放数据类型:数据、索引、插入缓冲bitmap页。
每张表的其他数据还是存放在共享表空间比如:回滚(undo)信息,插入缓冲索引页、系统事务信息、二次写缓冲等

表空间不会回收但是会重复利用。
数据段是B+树的叶子节点
索引段是B+树的非索引节点

常见的页类型
在这里插入图片描述

Innodb是面向行的数据库。还是Google Big Table是面向列的数据库。

MySQL的分区支持以下几种类型:
Range:一定连续区间的列值被放入分区
list:离散的值
hash:根据用户自定义的表达式来分区
key:根据mysql数据库的hash函数来分区

上面四种类型的分区,数据必须是整型的,如果不是整型,那么需要year()等函数转化为整型。

columns类型的分区支持以下的数据类型:

  • 所有的整型类型
  • 日期类型
  • 字符串类型
    在这里插入图片描述

分区是把数据按列的规则把数据聚集在一个分区中。

使用分区表的场景是:查询的条件中必须带分区列。
限制:

  • 一张表最多只能1024个分区
  • 分区表无法对非分区列建立唯一索引
  • 分区表无法使用外键
  • 当无法使用行锁时,会锁住全部分区
  • 分区列必须是唯一索引的

优点:

  • 可能降低了B+树的高度,减少了查询的IO

第五章 索引与算法

索引类型:B+树索引、全文索引、哈希索引。
按主键顺序存放的是聚集索引,其他是非聚集索引

查看表的索引

show index from table_name

在这里插入图片描述

  • seq_in_index:联合索引中该列的位置
  • collation:A表示按B+树存储在索引,NULL表示以hash桶存放索引数据
  • cardinality(基数):列中唯一条目的个数

cardinality值很关键,优化器会根据这个值来判断是否使用这个索引,但是这个值不是实时更新的。可以使用analyze table table_name来更新这个值。

MySQL5.6版本之后支持online DDL操作,允许辅助索引创建的同时,运行其他CRUD的DML操作。

  • 辅助索引的创建
  • 改变自增值
  • 添加或删除外键约束
  • 列的重命名

Innodb存储引擎碰到表在进行online ddl,会把dml的操作日志写入到一个缓存中。等到索引创建完成后重做到表上。 缓存的大小由innodb_online_alter_log_max_size控制。默认是128m

show index from table会触发cardinality的采样,cardinality是通过对8个页的数据采样得到的,每次选取的8个页,因此每次cardinality的值都不一样

覆盖索引:即从辅助索引中就可以得到查询的记录,而不需要查询聚集索引中的记录。使用辅助索引不需要查询整行记录的信息,因此可以减少大量IO操作。select count(*) from us_log 就会选择此表上的非聚集索引

使用辅助索引存在回表问题,当回表的量在大于表数据的20%左右时会使用主键索引,或者全部扫描

使用index hit,有两种语法select count(*) from us_log use index(us_log_phone_password)explain select count(*) from us_log force index(us_log_phone_password);

使用Multi-Range Read优化,目的是为了减少磁盘的随机访问,改为顺序访问。这对IO密集型SQL查询语句可带来性能极大的提示。适用于range,ref,eq-ref类型的查询。把索引中的rowId读到内存,排序后访问聚集索引。可以在执行计划中看到using index condition和using MRR的字眼。可以通过show variables like 'optimizer_switch'

使用Index Condition Pushdown(ICP)优化。支持range、ref、eq_ref、ref_or_null类型的查询。当使用ICP优化时,可以在extra看到using index condition提示。MYSQL数据库在取出索引的同时判断是否可以进行where条件的过滤,也就是将where的部分过滤操作放在了存储引擎层。减少查看数据行的IO次数。

第六章 锁

锁的兼容性
在这里插入图片描述

查看当前锁请求的信息:show engine innodb status

一致性非锁定读:使用MVCC技术,读取undo log中的快照数据。在RR隔离级别下,都是一致性非锁定读
一致性锁定读:使用select * from us lock in share modeselect * from us for update

自增长与锁
自增长的简单实现方式是:select max(id)+1 from table_name for update 通过锁表的方式或者最大的id。
显然上面的方式有严重的性能问题。因此还有一种方式是在内存中维护一个计数器,使用互斥量(mutex)在内存中操作。

自增列锁模式:默认是1,对于普通插入使用内存锁,对于批量插入使用auto-inc locking
在这里插入图片描述

行锁的三种算法
record lock:单个行记录上的锁
gap lock:间隙锁(防止幻读),锁定一个范围,但不包含记录本身。
next-key lock:Gap lock + Record lock,锁定一个范围,并且锁定记录本身。如果是唯一索引会降级为record lock。

表结构和数据
在这里插入图片描述

select * from z where b=3 for update 
-- 会锁住主键索引中id是5的行(记录锁)。会给(1-3)加上gap lock和3这条记录加上record lock 和给(3-6)加上gap lock

会阻塞
在这里插入图片描述
可以通过
在这里插入图片描述
锁问题:脏读、不可重复读、更新丢失。
读未提交的事务隔离级别会产生脏读,读已提交的事务隔离级别可以避免脏读问题,但是会有不可重复读问题。
可重复读的隔离级别可以避免不可重复读问题,但是无法避免更新丢失问题。使用串行化避免更新丢失问题。

阻塞问题:一个事务等待另一个事务释放资源的过程就称之为阻塞。

死锁:当两个或者两个以上的事务在执行过程中,出现互相等待资源的现在,称之为死锁。
使用wait-for graph检测死锁,需要锁的信息链表,事务等待链表

Innodb不存在锁升级问题,使用页作为锁的管理单元,并采用位图方式,占用内存较少。

第七章 事务

事务的四个特性:
A:原子性,事务内的sql要么一起成功要么一起失败。redo log保证
C:一致性。事务执行完后,数据从一个状态到另一个状态。 undo log
I:隔离性。使用锁来实现。
D:持久性 当事务写入成功后,那么数据不会丢失。redo log

redo log

基本概念
包括两部分,内存中的redo log buffer和redo log file。
使用使用force log at commit机制(会调用操作系统的fsync,把文件缓存刷盘),当事务提交时,先将重做日志文件进行持久化,保证了事务的持久性。重做日志包括redo log和undo log。
redo log基本是顺序写,undo log存在随机读写。

innodb提供了innodb_flush_log_at_trx_commit用来控制重做日志刷新到磁盘的策略。一共有三种选项0、1、2。0代表:事务提交时不写入重做日志,等待每秒的写入。1表示事务提交时调用fsync。2表示事务提交时,仅将重做日志写入到文件系统中重做日志文件的缓冲区

bin log是二进制日志,记录的是原始的sql语句,是MySQL层面的,用来主从同步和Point-in-time的恢复。
与redo log的差别:

特性 redo log bin log
写入的时机 每个影响数据的sql都会在执行时写入redo log 在事务提交时写入
写入的内容 对数据页的物理修改 记录原始的sql语句
作用 恢复事务数据,保证D和A。 用于数据恢复和主从同步

log block
重做日志和缓存都是以块的方式保存,称为重做日志块,每块大小为512k,与一个扇区相同,因此不需要double write技术。

共有三部分组成:日志块头(12字节),日志块尾(8字节),日志内容(492字节)。
在这里插入图片描述
log group
重做日志组,其实只有一组

重做日志格式
在这里插入图片描述

LSN
是Log Sequence Number的缩写,表示的是日志序列号。
每个页中会记录刷新时的LSN。
恢复时,只需要重新执行最后刷新的LSN到最新的LSN之间的数据。

恢复
redo log是二进制日志,记录的是物理修改。因此是幂等的,而且恢复速度大于bin log

undo log

在修改数据之前,把未修改的数据保存到undo log,用于一致性非锁定度和事务回滚时的恢复
存放在数据库内部的一个特殊段中,这个段称为undo段。undo段位于共享表空间

undo是逻辑日志。对于insert语句增加一条delete的sql,delete语句增加一条insert的sql。update增加一条反向语句.

purge

在事务中删除一行记录,并不会立刻删除行,只会把行记录的delete_flag置为1,等待后续的purge thread做正在的清理。
update操作也类似。

group commit

多个commit一起fsync

第八章 备份与恢复

按备份的方法分为热备(online 备份)、冷备(offline 备份)、温备(对当前允许的数据库有影响,增加全局读锁实现备份数据一致性)

按备份后的文件内容分为:逻辑备份(逻辑sql语句)和裸文件备份(物理文件)

按备份数据库的内容分为:完全备份(完整的备份)、增量备份(在上一次的基础上备份)、日志备份(对二进制的日志备份,主要百科Mysql的数据库复制,主从。)

逻辑备份
使用mysqldump工具可以完成数据库的转存,用于不同数据库之间的升迁。
执行mysql -uroor -p <test_backup.sql可以完成mysqldump的恢复

二进制日志的备份
使用mysqlbinlog binlog.00000001 |mysql -u root -p test可以恢复数据库

热备
使用ibbackup和xtrabackup可以完成热备,第三方工具

增量备份
xtrabackup可以实现增量备份

复制

Mysql的复制主要分为三个步骤进行:
1.主服务器把数据更改记录到bin log
2.从服务器同步bin log并写入到自己的relay log中
3.从服务器重做中继日志中的日志,把更改应用到自己的数据库,完成一次数据备份

这个过程是异步实时的。
在这里插入图片描述
当从节点复制延迟较久时,可以增大IO线程、SQL线程的个数,加快复制速度。
使用show slave status
在这里插入图片描述

第九章 性能调优

Mysql是可以使用多核CPU的。 一般机器的配置可以调整innodb_read_io_threads和innodb_write_io_threads的值跟cpu核数相同。

机器的内存可以用来缓存索引和数据。

内存越大缓存索引越多,查询效率越高。
插入buffer越大肯定插入的性能越好。

硬盘的影响

主要是随机写、随机读、顺序写和顺序读四个性能指标

对于普通的机械硬盘,顺序访问的速度远高于随机访问的速度。因为机械盘读取需要磁头旋转和定位,顺序消耗的时间最少。

固态硬盘的内部是闪存,属于电子设备,没有磁头。所以随机访问的速度远远大于机械硬盘。

固态和机械硬盘的延时
在这里插入图片描述

RAID

RAID:redundant array of independent disks。是把多个相对便宜的硬盘组合起来,称为一个磁盘数组,比肩大磁盘。 多个硬盘是一个逻辑扇区,操作系统会认为是一个硬盘。

RAID的作用:

  • 增强数据集成度
  • 增强容错功能
  • 增加处理量或容量

raid0:把多个磁盘组成一个逻辑磁盘。利用率100%,容错率0%
raid1:把两个磁盘组成镜像磁盘,利用率50%,容错率50%
在上述两种基础上,出来了raid10和raid01。区别在于是先组成镜像再分区,还是先分区再组成镜像。
一般来说是raid10比较好

write back vs write through
回写是数据写入到raid卡的缓存就返回,只要raid有备份电源,那么缓存中的数据就是安全的,可以保证数据的一致性
只写是不开启raid卡的缓存,写入磁盘才算成功。

本文地址:https://blog.csdn.net/wengfuying5308/article/details/107145319

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网