MySQL - [11] InnoDB存储引擎
Page 页。是真正理解InnoDB存储引擎的入口。
一、Page —— 页
1.1、InnoDB 数据页及其结构
- 为了避免一条一条读取磁盘数据,InnoDB采取页的方式,作为磁盘和内存之间交互的基本单位,一个页的大小一般是16KB。
- InnoDB 为了不同的目的而设计了多种不同类型的页、存放undo日志信息的页等等。
- 我们把存放表中数据记录的页,称为 索引页 or 数据页。往数据页(User Records部分)中存储数据(也叫“记录”)。
查看innodb_page_size大小:
show variables like 'innodb_page_size';
(value的值单位为byte)
1.2、记录的头信息
>> deleted_flag
:逻辑删除标记(0:未删除,1:已删除)
>> min_rec_flag
:B+树中每层非叶子节点中的最小的目录项记录,都会添加该标记。
>> n_owned
:一个页面被分若干组后,“带头大哥”用于保存组中所有的记录条数。
>> heap_no
:表示当前记录在页面堆中的相对位置。
>> record_type
:表示当前的记录类型
① 0:普通记录
② 1:B+数非叶子节点的目录项记录
③ 2:表示Infimum记录
④ 3:表示Supremum记录
>> next_record
:表示下一条记录的相对位置,也就是链表。它表示从当前记录的真实数据到下一条的真实数据的距离。
1.3、Page Directory概述
记录在页中是按照主键值从小到大的顺序串联称为一个单向链表,因此查询也只能以头节点开始逐一向后查询,但是如果数据量很大,那么性能就无法保证了。针对这个问题,InnoDB采取了图书目录的解决方案,即:Page Directory。
分组规则如下所示:
① 对于Infimum记录所在的分组只能有1条记录。
② 对于Supremum记录所在的分组只能在1~8条记录之间。
③ 剩下的其他记录所在的分组只能在4~8条记录之间。
分组步骤如下:
① 初始情况下,一个数据页中只有Infimum记录和Supremum记录这两条,所以分为两个组。
② 之后每当插入一条记录时,都会从页目录中找到对应记录的主键值比待插入记录的主键值大,并且差值最小的槽,然后把该槽对应的n_owned加1.
③ 当一个组中的记录数等于8时,当再插入一条记录的时候,会将该组中的记录拆分成两个组(一个组中4条记录,另一个组中5条记录)。并在拆分过程中,会在Page Directory中新增一个槽,并记录这个新增分组中最大的那条记录的偏移量。
二、Index —— 索引
如何加快记录的查询?
当记录越来越多,创建的页也会越来越多,如果仅通过链表方式遍历查询,性能会出现很大问题。如何解决?采用B+树结构,即:叶子节点里存储完整的数据(数据页),非叶子节点存储主键索引(索引页)。
三、Buffer Pool —— 缓冲池
3.1、缓冲池概述
为了缓存磁盘中的页,MySQL服务器启动时就向操作系统申请了一片连续的内存空间,他们给这片内存起名为Puffer Pool(缓冲池)。默认Buffer Pool只有128M,可以在启动服务器的时候配置innodb_buffer_pool_size
(单位为字节)启动项来设置自定义缓冲池大小。Buffer Pool对应的一片连续的内存被划分为若干个页面,默认也是16kb,该页面称为缓冲页。为了更好地管理Buffer Pool中的这些缓冲页,InnoDB为每个缓冲页都创建了控制块,它与缓冲页是一一对应的。
3.2、Free链表
Buffer Poll的初始化过程中,是先向操作系统申请连续的内存空间,然后把它划分成若干个【控制块&缓冲页】对儿。当插入数据的时候,为了能够知道哪些缓冲页是空闲且可分配的,MySQL把所有空闲的缓冲页对应的控制块作为一个节点放到一个链表中,这个链表便称之为Free链表。
3.3、Flush链表
如果我们修改了Buffer Pool中某个缓冲页的数据,那么它就与磁盘上的页不一致了,这样的缓冲页也被称之为脏页(dirty page)。为了性能问题,我们每次修改缓冲页后,并不着急立刻把修改刷新到磁盘,而是将被修改过的缓冲页对应的控制块作为节点加入到这个链表中,该链表也被称为flush链表。
Flush链表刷新方式有如下两种:
【1】从flush链表中刷新一部分页面到磁盘
>> 后台线程也会定时从flush链表中刷新一部分页面到磁盘,刷新的速率取决于当时系统是否繁忙。—— 即:BUF_FLUSH_LIST
>> 有时后台线程刷新脏页的进度比较慢,导致用户准备加载一个磁盘页到Buffer Pool中时没有可用的缓冲页。此时,就会尝试查看LRU链表尾部,看是否存在可以直接释放掉的未修改缓冲页。如果没有,则不得不将LRU链表尾部的一个脏页同步刷新到磁盘(与磁盘交互是很慢的,这会降低处理用户请求的速度)。 —— 即:BUF_FLUSH_SINGLE_PAGE
【2】从LRU链表的冷数据中刷新一部分页面到磁盘
>> 后台线程会定时从LRU链表的尾部开始扫描一些页面,扫描的页面数量可以通过系统变量innodb_lru_scan_depth来指定,如果在LRU链表中发现脏页,则把他们刷新到磁盘。—— 即: BUF_FLUSH_LRU
>> 控制块里会存储该缓冲页是否被修改的信息,所以在扫描LRU链表时,可以很轻松地获取到某个缓冲页是否脏页的信息。
3.4、LRU链表
>> 线性预读:如果顺序访问某个区(extent,一个区默认64个页)的页面超过了innodb_read_ahead_threshold(默认56)的值,就会触发一次异步读取下一个区中全部的页到Buffer Pool中的请求。
>> 随机预读:如果开启了随机预读功能(默认:innodb_random_read_ahead=OFF),如果某个区(extent)有13个连续的页面都已经被加载到了Buffer Pool中,无论这些页面是不是顺序读取的,都会触发一次异步读取本区全部的页到Buffer Pool中的请求。
针对LRU链表方案缺点的优化
(1)针对预读的优化
InnoDB规定,当磁盘上的某个页在初次加载(只是加载,没有涉及读取)到Buffer Pool中的某个缓冲页时,该缓冲页对应的控制块会被放到old区域的头部。这样预读页就只会在old区域,不会影响young区域中使用比较频繁的缓冲页。
(2)针对全表的优化
虽然首次加载放到的是old区域的头部,但是由于是全表扫描,会对加载的数据进行访问,那么第一次访问的时候,就会将该页放到young区域的头部。这样仍然会把哪些使用频率比较高的页面给“排挤”下去。
那怎么办呢?由于全表扫描有一个特点,就是它对某个页的频繁访问且总耗时很短。所以,针对这种情况,InnoDB规定,在对某个处于old区域的缓冲页进行第一次访问时,就在它对应的控制块中记录下这个访问时间,如果后续的访问时间与第一次访问的时间在某个时间间隔内(即:innodb_old_blocks_time,默认为1000,单位为ms),那么该页面就不会从old区域移动到young区域的头部,否则将它移动到young区域的头部。
四、redo日志
4.1、什么是redo日志
如果我们只在内存的Buffer Pool中修改了页面,假设在事务提交后突然发生了某个故障,导致内存中的数据都失效了,那么这个已经提交的事务在数据库中所作的更改也就丢失了。针对这种问题,怎么处理呢?
【方案1】在事务提交时,把该事务修改的所有页面都刷新到磁盘,刷新成功了才提示事务提交成功。
① 刷新一个完整的数据页太浪费了
虽然我们只修改了一条记录,但是会将这条记录所在的页(16kb)都刷新到磁盘上,会造成大量磁盘I/O的浪费。
② 随机I/O刷新起来比较慢
一个事务可能包含很多语句,即使是一条语句也可能修改许多页面,并且该事务修改的这些页面可能并不相邻。这就意味着将某个事务修改额Buffer Pool中的页面刷新到磁盘时,需要进行很多的随机I/O。而随机I/O要比顺序I/O慢,尤其是机械硬盘。
【方案2】在事务提交时,只需要把修改的内容记录一下就好了
例如:“将第0号表空间第100号页面中偏移量为1000处的值更新为2。”
4.2、redo简单日志类型
在对页面的修改是极其简单的情况下,redo日志中只需要记录一下在某个页面的某个偏移量处修改了几个字节的值、具体修改后的内容是什么就好。比如,操作Max Row id。
=>> MLOG_1BYTE:表示在页面的某个偏移量处写入1字节的redo日志类型。
=>> MLOG_2BYTE:表示在页面的某个偏移量处写入2字节的redo日志类型。
=>> MLOG_4BYTE:表示在页面的某个偏移量处写入4字节的redo日志类型。
=>> MLOG_8BYTE:表示在页面的某个偏移量处写入8字节的redo日志类型。
=>> MLOG_WRITE_STRING:表示在页面的某个偏移量处写入了一个字节序列。
4.3、redo复杂日志类型 —— MLOG_COMP_REC_INSERT
4.4、redo日志组
在执行语句的过程中产生的redo日志,被InnoDB划分成了若干个不可分割的组。比如:更新Max Row ID属性时产生的redo日志为一组,是不可分割的;向聚簇索引/二级索引对应B+树的页面中插入一条记录时产生的redo日志为一组,是不可分割的等等。
InnoDB认为,比如向某个索引对应的B+树中插入一条记录的过程必须是原子的,不能说插入了一半之后就停止了。否则就会形成一棵不正确的B+树。所以他们规定在执行这些需要保证原子性的操作时,必须以组的形式来记录redo日志。在进行恢复时,针对某个组中的redo日志,要么把全部的日志都恢复,要么一条也不恢复。
4.5、MTR(Mini-Transaction)
对底层页面进行一次原子访问的过程被称为一个Mini-Transaction(MTR)。
事务、语句、MTR、redo日志之间的关系,如下图所示:
① 1个事务可以包含N条SQL语句
② 1条SQL语句可以包含N个MTR
③ 1条MTR可以包含N条redo日志
4.6、查看redo日志相关配置信息
-- 查看数据目录所在位置
show variables like 'datadir';
-- 查看redo日志文件所在的目录,默认为当前的数据目录
show variables like 'innodb_log_group_home_dir';
-- 查看每个redo日志文件的大小,默认值为48MB
show variables like 'innodb_log_file_size';
-- 查看redo日志文件的个数,默认值为2,最大值为100个
show variables like 'innodb_log_files_in_group';
五、undo日志
5.1、什么时undo日志
事务是需要保证原子性的,也就是说,事务中的操作要么全部完成,要么什么也不做。但有如下情况,会造成事务执行不完。
① 事务执行过程中可能遇到各种错误,比如:服务器宕机,操作系统异常,突然断电......
② 程序在事务执行过程中手动输入rollback语句结束当前事务的执行。
遇到上面的情况,为了保证事务的原子性,我们需要把数据还原回原来的样子,这个过程就叫做回滚(rollback)
数据库为了回滚而记录的日志,我们就称之为撤销日志(undo log)
注意一点,由于select操作并不会修改任何记录,所以并不需要记录相应的undo日志。
5.2、如何开启事务
开启的事务类型 | 解释 |
只读事务 | 通过对START TRANSACTION READ ONLY语句开启一个只读事务,在只读事务中,不可以对普通表进行增删改操作;但可以对临时表进行增删改操作 |
读写事务 | 通过对START TRANSACTION READ WRITE语句开启一个读写事务。使用BEGIN、START TRANSACTION语句开启的事务,默认也算是读写事务。在读写事务中可以对表执行增删改查操作。 |
5.3、何时分配事务id
只有在事务对表中的记录进行改动时才会为这个事务分配一个唯一的事务id,否则事务id默认为0。
(1)只读事务何时分配事务id
只有在它第一次对某个用户创建的临时表(CREATE TEMPORARY TABLE)执行增删改操作时,才会为这个事务分配一个事务id,否则是不分配的。
(2)读写事务何时分配事务id
只有在它第一次对某个表(包括用户创建的临时表)执行增删改操作时,才会为这个事务分配一个事务id,否则是不分配的。
5.4、事务id是怎么生成的
事务id本质上就是一个数字,事务id的生成策略如下:
① 内存中维护一个全局变量,每当需要为某个事务分配事务id时,就会把该变量值当作事务id分配给该事务,并且自增1
② 每当这个变量的值为256的倍数时,就会将该值刷新到系统表空间中页号为5的页面中一个名为Max Trx ID的属性中(占用8个字节)
③ 当系统下一次启动时,会将Max Trx ID的值加载到内存中,并加上256之后赋值给前面提到的全局变量。
为什么要加256?因为上次关机时,该全局变量的值可能大于磁盘页面中的Max Trx ID属性值。
5.5、事务id在记录中存储的位置
trx_id表示对该条记录进行改动的语句所对应的事务id
不同版本查询table_id的方式
【mysql 5.x】select * from information_schema.innodb_sys_tables;
【mysql 8.x】select * from information_schema.innodb_tables;
— 要养成终身学习的习惯 —
MySQL - [11] InnoDB存储引擎的更多相关文章
- MySQL数据库InnoDB存储引擎多版本控制(MVCC)实现原理分析
文/何登成 导读: 来自网易研究院的MySQL内核技术研究人何登成,把MySQL数据库InnoDB存储引擎的多版本控制(简称:MVCC)实现原理,做了深入的研究与详细的文字图表分析,方便大家理解I ...
- MySQL数据库InnoDB存储引擎中的锁机制
MySQL数据库InnoDB存储引擎中的锁机制 http://www.uml.org.cn/sjjm/201205302.asp 00 – 基本概念 当并发事务同时访问一个资源的时候,有可能 ...
- MySql中innodb存储引擎事务日志详解
分析下MySql中innodb存储引擎是如何通过日志来实现事务的? Mysql会最大程度的使用缓存机制来提高数据库的访问效率,但是万一数据库发生断电,因为缓存的数据没有写入磁盘,导致缓存在内存中的数据 ...
- MySQL数据库InnoDB存储引擎
MySQL数据库InnoDB存储引擎Log漫游 http://blog.163.com/zihuan_xuan/blog/static/1287942432012366293667/
- mysql中InnoDB存储引擎的行锁和表锁
Mysql的InnoDB存储引擎支持事务,默认是行锁.因为这个特性,所以数据库支持高并发,但是如果InnoDB更新数据的时候不是行锁,而是表锁的话,那么其并发性会大打折扣,而且也可能导致你的程序出错. ...
- mysql之innodb存储引擎
mysql之innodb存储引擎 innodb和myisam区别 1>.InnoDB支持事物,而MyISAM不支持事物 2>.InnoDB支持行级锁,而MyISAM支持表级锁 3>. ...
- 一文带你读懂 Mysql 和 InnoDB存储引擎
作为一名开发人员,在日常的工作中会难以避免地接触到数据库,无论是基于文件的 sqlite 还是工程上使用非常广泛的 MySQL.PostgreSQL,但是一直以来也没有对数据库有一个非常清晰并且成体系 ...
- 在MySQL的InnoDB存储引擎中count(*)函数的优化
写这篇文章之前已经看过了很多数据库方面的优化内容,大部分都是加索引.使用事务.要什么select什么等等.然而,只是停留在阅读的层面上,很少有实践,因为没有遇到真实的项目,一切都是纸上谈兵.实践是检验 ...
- MySQL:InnoDB存储引擎的B+树索引算法
很早之前,就从学校的图书馆借了MySQL技术内幕,InnoDB存储引擎这本书,但一直草草阅读,做的笔记也有些凌乱,趁着现在大四了,课程稍微少了一点,整理一下笔记,按照专题写一些,加深一下印象,不枉读了 ...
- MySQL 温故而知新--Innodb存储引擎中的锁
近期碰到非常多锁问题.所以攻克了后,细致再去阅读了关于锁的书籍,整理例如以下:1,锁的种类 Innodb存储引擎实现了例如以下2种标准的行级锁: ? 共享锁(S lock),同意事务读取一行数据. ? ...
随机推荐
- django admin 后台管理 新手学习步骤记录 (2)
学习使用django admin后台管理. 参考.Django基础之Admin后台数据管理_django admin_马航行的博客-CSDN博客
- AD使用插件生成交互式BOM
AD使用插件生成交互式BOM 效果图镇楼: 下面来说一下怎么搞,过程其实也很简单,就加载一个脚本的事儿. 1.下载AD用交互式BOM插件 首先前往GitHub下载这位大佬开发的插件 地址:https: ...
- 【人工智能】【Python】Numpy基础
Numpy 目录 Numpy Numpy简介 ndarray与原生Python List运算效率对比 N阶数组 ndarray (1)创建数组 (2)生成数组 生成纯1数组 生成纯0数组 从现有数组生 ...
- iOS app 自动化测试,appium inspector 启动会话报错:Failed to create session. An unknown server-side error occurred while processing the command. Original error: '12.5.5' does not exist in the list of simctl SDKs.
报错内容:Failed to create session. An unknown server-side error occurred while processing the command. O ...
- x509.MarshalSm2PrivateKey
根据搜索结果,x509.MarshalSm2PrivateKey 函数需要两个参数:一个 *sm2.PrivateKey 和一个 []byte 类型的密码.以下是使用 x509.MarshalSm2P ...
- SQL语句报com.alibaba.druid.sql.parser.ParserException: TODO IDENTIFIER cross
这个错误根据网络上人员说是解析出错!虽然报错但不影响结果!但是报错了就是看的不爽!把druid包换成druid-1.0.9.jar就解决这个问题了!至于性能暂时还没测试到
- Qt音视频开发01-共享解码线程(耗时一年/性能凶残/至臻完美)
一.前言 大概在8年前就开始用ffmpeg做视频解码的显示,第一个版本就100行代码左右,功能极其简单,就是开个线程解码视频流转成图片发给主界面绘制.时间过得真快,从当初的一胎到现在二胎都上学了三胎计 ...
- Qt编写的项目作品7-视频监控系统
一.功能特点 (一)软件模块 视频监控模块,各种停靠小窗体子模块,包括设备列表.图文警情.窗口信息.云台控制.预置位.巡航设置.设备控制.悬浮地图.网页浏览等. 视频回放模块,包括本地回放.远程回放. ...
- Qt编写的项目作品31-PDF阅读器(雨田哥作品)
一.功能特点 仿WPS界面. 预览PDF文件. 支持PDF预览放大.缩小. 支持目录预览查看. 支持目录点击跳转页查看. 支持页数指定跳转. 支持上一页.下一页.首页.尾页跳转. 支持鼠标拖拽滑动预览 ...
- 给 Python 添加进度条 | 给小白的 tqdm 精炼实例!
给 Python 添加进度条 | 给小白的 tqdm 精炼实例! 假设我们有一个循环: for i in range(100): do_something() # 这里做某些事 假设 do_somet ...