目录

      • 页(Page)是 Innodb 存储引擎用于管理数据的最小磁盘单位
        • B+树的一般高度
        • 记录在页中的存储
      • innodb ibd文件
        • innodb 页类型
        • 分析`ibd`文件
          • 查看数据表的行格式
          • 查看ibd文件
        • 分析 ibd的第4个页:`B-tree Node`类型
          • 先分析File Header(38字节-描述页信息)
          • 再分析Page Header(56字节-记录页的状态信息)
          • 分析Infimum + Supremum Record (26字节-两个虚拟行记录)
          • User Record(表中的数据记录)
          • COMPACT行记录格式
          • File Tailer(最后8字节)
      • 附:二进制文件查看小技巧
      • 页分裂/页合并
        • innodb数据的存储
        • 页合并
        • 页分裂
      • 附加:mysql频繁的插入删除导致的问题
        • 1. 性能下降
        • 2. 碎片化
        • 3. 锁定和阻塞
        • 4. 日志文件增长
        • 5. 资源竞争

页(Page)是 Innodb 存储引擎用于管理数据的最小磁盘单位

innoDB 中页的默认大小是 16KB

假设一行记录的数据大小为1k,那么单个叶子节点(页)中的记录数=16K/1K=16

在这里插入图片描述

  1. File Header: 文件头部,页的一些通用信息(38字节)
  2. page Header: 页面头部,数据页专有的一些信息(56字节)
  3. infimum+supremum: 行记录最小值和最大值,两个虚拟的行记录(26字节)
  4. user recorders: 实际存储的行记录内容(不确定)
  5. free space: 页中尚未使用的空间(不确定)
  6. Page Directory: 页中的某些记录的相对位置(不确定)
  7. File Tailer: 校验页是否完整(8字节)
B+树的一般高度

即非叶子节点能存放多少指针?

假设主键ID为bigint类型,长度为8字节,而指针大小在InnoDB源码中设置为6字节,这样一共14字节,一个页中能存放多少这样的单元,其实就代表有多少指针,即16kb/14b=1170;那么可以算出一棵高度为2的B+树,大概能存放1170*16=18720条这样的数据记录(即1170个索引,每个索引定位叶子节点的一页数据,一页能存储16行原始数据)。

根据同样的原理我们可以算出一个高度为3的B+树大概可以存放:1170*1170*16=21,902,400行数据。所以在InnoDB中B+树高度一般为1-3层,它就能满足千万级的数据存储。在查找数据时一次页的查找代表一次IO,所以通过主键索引查询通常只需要1-3次逻辑IO操作即可查找到数据。

记录在页中的存储
  1. 当一个记录需要插入页的时候,会从Free space划分空间到User recorders
  2. Free Space部分的空间全部被User Records部分替代掉之后,也就意味着这个页使用完了,如果还有新的记录插入的话,就需要去申请新的页了

innodb ibd文件

ibd文件是以为单位进行管理的,页通常是以16k为单位,所以ibd文件通常是16k的整数倍

innodb 页类型
名称十六进制解释
FIL_PAGE_INDEX0x45BFB+树叶节点
FIL_PAGE_UNDO_LOGO0x0002UNDO LOG页
FIL_PAGE_INODE0x0003索引节点
FIL_PAGE_IBUF_FREE_LIST0x0004InsertBuffer空闲列表
FIL_PAGE_TYPE_ALLOCATED0x0000该页的最新分配
FIL_PAGE_IBUF_BITMAP0x0005InsertBuffer位图
FIL_PAGE_TYPE_SYS0x0006系统页
FIL_PAGE_TYPE_TRX_SYS0x0007事务系统数据
FIL_PAGE_TYPE_FSP_HDR0x0008FILE SPACE HEADER
FIL_PAGE_TYPE_XDES0x0009扩展描述页
FIL_PAGE_TYPE_BLOB0x000ABLOB页
分析ibd文件
mubi@mubideMacBook-Pro bin $ mysql -uroot -p123456
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 109
Server version: 5.6.40 MySQL Community Server (GPL)Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.mysql> use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -ADatabase changed
mysql> select * from tb_user;
+------------+--------+----------+-------+-------------+----------+
| id         | userID | password | name  | phone       | address  |
+------------+--------+----------+-------+-------------+----------+
|          1 | 00001  | 123456   | zhang | 15133339999 | Shanghai |
|          2 | 00002  | 123456   | wang  | 15133339999 | Beijing  |
|          4 | 0003   | NULL     | abc   | NULL        | NULL     |
|          6 | 0003   | NULL     | abc   | NULL        | NULL     |
|          7 | 0004   | 123456   | tom   | 110         | beijing  |
|         10 | 0004   | 123456   | tom   | 110         | beijing  |
| 2147483647 | 0004   | 123456   | tom   | 110         | beijing  |
+------------+--------+----------+-------+-------------+----------+
7 rows in set (0.00 sec)mysql>
查看数据表的行格式
mysql> show table status like 'tb_user'\G;
*************************** 1. row ***************************Name: tb_userEngine: InnoDBVersion: 10Row_format: CompactRows: 6Avg_row_length: 2730Data_length: 16384
Max_data_length: 0Index_length: 16384Data_free: 0Auto_increment: 2147483647Create_time: 2020-03-21 16:10:06Update_time: NULLCheck_time: NULLCollation: utf8_general_ciChecksum: NULLCreate_options:Comment:
1 row in set (0.00 sec)ERROR:
No query specifiedmysql>
查看ibd文件

在这里插入图片描述

使用py_innodb_page_info工具(https://github.com/happieme/py_innodb_page_info

在这里插入图片描述

注意到文件大小114688字节(114688 = 16 * 1024 * 7)即有7个页(要分析哪个页直接定位到二进制文件到开始,然后分析即可)

分析 ibd的第4个页:B-tree Node类型

page offset 00000003, page type <B-tree Node>, page level <0000>

>>> hex(3 * 16 * 1024)
'0xc000'
>>> hex(4 * 16 * 1024)
'0x10000'
>>>
先分析File Header(38字节-描述页信息)

在这里插入图片描述

  • 2D A1 2D 57 -> 数据页的checksum值
  • 00 00 00 03 -> 页号(偏移量),当前是第3页
  • FF FF FF FF -> 目前只有一个数据页,无上一页
  • FF FF FF FF -> 目前只有一个数据页,无下一页
  • 00 00 00 04 6F 65 24 CF -> 该页最后被修改的LSN
  • 45 BF -> 页的类型,0x45BF代表数据页,刚好这页是数据页
  • 00 00 00 00 00 00 00 00 -> 独立表空间,该值为0
  • 00 00 00 06 -> 表空间的SPACE ID
再分析Page Header(56字节-记录页的状态信息)

参见:innodb-page-header

在这里插入图片描述

标识字节数解释本次值:说明
PAGE_N_DIR_SLOTS2number of directory slots in the Page Directory part; initial value = 200 02,2个槽位
PAGE_HEAP_TOP2record pointer to first record in heap01 ED,堆第一个开始位置的偏移量,也即空闲偏移量
PAGE_N_HEAP2number of heap records; initial value = 280 0A
PAGE_FREE2record pointer to first free record01 1C
PAGE_GARBAGE2“number of bytes in deleted records”00 20,删除的记录字节
PAGE_LAST_INSERT2record pointer to the last inserted record01 C5,最后插入记录的位置偏移
PAGE_DIRECTION2either PAGE_LEFT, PAGE_RIGHT, or PAGE_NO_DIRECTION00 02,自增长的方式进行行记录的插入,方向向右
PAGE_N_DIRECTION2number of consecutive inserts in the same direction, for example, “last 5 were all to the left”00 02
PAGE_N_RECS2number of up[ser records00 07,共7条有效记录数
PAGE_MAX_TRX_ID8the highest ID of a transaction which might have changed a record on the page (only set for secondary indexes)00 00 00 00 00 00 00 00
PAGE_LEVEL2level within the index (0 for a leaf page)00 00
PAGE_INDEX_ID8identifier of the index the page belongs to00 00 00 00 00 00 00 16
PAGE_BTR10“file segment header for the leaf pages in a B-tree” (this is irrelevant here)00 00 00 06 00 00 00 02 00 F2
PAGE_LEVEL10“file segment header for the non-leaf pages in a B-tree” (this is irrelevant here)00 00 00 06 00 00 00 02 00 32
  • 0xc000 + 01 ED = 0xC1ED地址后面的都是空闲的

在这里插入图片描述

  • 0xc000 + 01 C5 = 0xC1C5最后一条记录
    在这里插入图片描述
分析Infimum + Supremum Record (26字节-两个虚拟行记录)

infimum: n. 下确界;
supremum: n. 上确界;

Infimum和Suprenum Record用来限定记录的边界,Infimum是比页中任何主键值都要小的值,Suprenum是指比任何页中可能大值还要大的值,这两个值在页创建时被建立,并且在任何情况下都不会被删除。Infimum和Suprenum与行记录组成单链表结构,查询记录时,从Infimum开始查找,如果找不到结果会直到查到最后的Suprenum为止,然后通过Page Header中的FIL_PAGE_NEXT指针跳转到下一个page继续从Infimum开始逐个查找

在这里插入图片描述

#Infimum伪行记录
01 00 02 00 20/*recorder header*/
69 6E 66 69 6D 75 6D 00/*只有一个列的伪行记录,记录内容就是Infimum(多了一个0x00字节)
*/
#Supremum伪行记录
08 00 0B 00 00/*recorder header*/
73 75 70 72 65 6D 75 6D/*只有一个列的伪行记录,记录内容就是Supremum*/

infimum行记录的recorder header部分,最后2个字节位00 20表示下一个记录的位置的偏移量

User Record(表中的数据记录)

用户所有插入的记录都存放在这里,默认情况下记录跟记录之间没有间隙,但是如果重用了已删除记录的空间,就会导致空间碎片。每个记录都有指向下一个记录的指针,但是没有指向上一个记录的指针。记录按照主键顺序排序:即用户可以从数据页最小记录开始遍历,直到最大的记录,这包括了所有正常的记录和所有被delete-marked记录,但是不会访问到被删除的记录(PAGE_FREE)

COMPACT行记录格式

在这里插入图片描述

  • 行格式的首部是一个非NULL变长字段长度列表,而且是按照列的顺序逆序放置的。当列的长度小于255字节,用1字节表示,若大于255个字节,用2个字节表示,变长字段的长度最大不可以超过2个字节(这也很好地解释了为什么MySQL中varchar的最大长度为65535,因为2个字节为16位,即pow(2,16)-1=65536)。第二个部分是NULL标志位,该位指示了该行数据中是否有NULL值,1个字节表示;该部分所占的字节应该为bytes;接下去的部分是为记录头信息(record header),固定占用5个字节(40位),每位的含义如下

  • 预留位1 1(bit位) 没有使用

  • 预留位2 1 没有使用

  • delete_mask 1 标记该记录是否被删除

  • min_rec_mask 1 标记该记录是否为B+树的非叶子节点中的最小记录

  • n_owned 4 表示当前槽管理的记录数

  • heap_no 13 表示当前记录在记录堆的位置信息

  • record_type 3 表示当前记录的类型,0表示普通记录,1表示B+树非叶节点记录,2表示最小记录,3表示最大记录

  • next_record 16 表示下一条记录的相对位置

在这里插入图片描述

File Tailer(最后8字节)

在这里插入图片描述

7E 75 29 30 6F 65 24 CF

注意到File Header该页最后被修改的LSN:00 00 00 04 6F 65 24 CF,可以看到后4个字节和File Tailer的后4个字节相同

附:二进制文件查看小技巧

  • 使用python可以方便的进行二进制相关的转换
    1. hex(16) # 10进制转16进制
    2. oct(8) # 10进制转8进制
    3. bin(8) # 10进制转2进制
>>> hex(6 * 16 * 1024)
'0x18000'
>>> hex(3 * 16 * 1024)
'0xc000'
>>>
  • vscode可以安装hexdump for VSCode插件

页分裂/页合并

innodb-page-merging-and-page-splitting

InnoDB不是按行的来操作的,它可操作的最小粒度是页,页加载进内存后才会通过扫描页来获取行/记录,curd操作则会产生页合并页分裂操作。

innodb数据的存储

在 InnoDB 存储引擎中,所有的数据都被逻辑地存放在表空间中,表空间(tablespace)是存储引擎中最高的存储逻辑单位,在表空间的下面又包括段(segment)、区(extent)、页(page), 页中存放实际的数据记录行
在这里插入图片描述
MySQL 使用 InnoDB 存储表时,会将表的定义和数据相关记录、索引等信息分开存储,其中前者存储在.frm文件中,后者存储在.ibd文件中(ibd文件既存储了数据也存储了索引)
在这里插入图片描述
在创建表时,会在磁盘上的 datadir 文件夹中生成一个 .frm 的文件,这个文件中包含了表结构相关的信息

页合并

删除记录时会设置record的flaged标记为删除,当一页中删除记录超过MERGE_THRESHOLD(默认页体积的50%)时,InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用。例如:合并操作使得页#5保留它之前的数据,并且容纳来自页#6的数据。页#6变成一个空页,可以接纳新数据。

页分裂

页可能填充至100%,在页填满了之后,下一页会继续接管新的记录。但如果下一页也没有足够空间去容纳新(或更新)的记录,那么就必须创建新的页了,如下

  1. 创建新页
  2. 判断当前页(页#10)可以从哪里进行分裂(记录行层面)
  3. 移动记录行
  4. 重新定义页之间的关系
#9 #10 #11 #12 #13 ...#8 #10 #14 #11 #12 #13 ...

如上,页#10没有足够空间去容纳新记录,页#11也同样满了, #10要分列为两列, 且页的前后指针关系要发生改变

附加:mysql频繁的插入删除导致的问题

1. 性能下降

频繁的插入和删除操作会增加数据库的工作量,特别是在高并发环境下,可能导致查询响应时间变长。

解决方案:

优化查询:确保你的查询是有效且高效的,使用适当的索引。

批量操作:尽可能使用批量插入(INSERT INTO … VALUES (), (), …)和批量删除(DELETE FROM WHERE id IN (…)) 而不是单条记录操作。

分区表:对于非常大的表,考虑使用分区表,这可以改善查询性能和减少单个表的维护负担。

2. 碎片化

频繁的插入和删除操作会导致数据文件和索引文件碎片化,这会降低查询效率。

解决方案:

定期重建索引:使用OPTIMIZE TABLE命令来重建表的索引,这可以帮助减少碎片。

归档旧数据:定期归档旧数据到另一个表或数据库中,减少主表的负担。

3. 锁定和阻塞

在高并发环境下,频繁的插入和删除操作可能导致行级锁或表级锁,从而引起阻塞和死锁。

解决方案:

减少锁的粒度:考虑使用较低级别的隔离级别(如READ COMMITTED),或者使用乐观锁策略。

锁优化:分析并优化事务的设计,确保事务尽可能短,避免长事务。

使用锁定的最小范围:尽可能在WHERE子句中指定具体的条件来减少锁定的范围。

4. 日志文件增长

频繁的写入操作会增加二进制日志(如binlog)和重做日志(如InnoDB的redo log)的大小,这可能会影响磁盘I/O性能。

解决方案:

配置合适的日志文件大小:调整max_binlog_size或innodb_log_file_size参数以控制日志文件的大小。

定期清理日志文件:使用PURGE BINARY LOGS命令来删除旧的二进制日志文件。

5. 资源竞争

在高负载情况下,频繁的插入和删除可能会增加CPU和内存的使用率,尤其是在多核服务器上。

解决方案:

硬件升级:如果可能,增加服务器的CPU核心数或内存容量。

负载均衡:使用数据库复制或分片策略来分散负载。

监控和调优:定期监控数据库性能,根据需要调整配置。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/84134.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/84134.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/84134.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【优选算法】C++滑动窗口

1、长度最小的子数组 思路&#xff1a; class Solution { public:int minSubArrayLen(int target, vector<int>& nums) {// 滑动窗口// 1.left0,right0// 2.进窗口( nums[right])// 3.判断// 出窗口// (4.更新结果)// 总和大于等于 target 的长度最小的 子数组…

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…

408考研逐题详解:2009年第33题

2009年第33题 在 OSI 参考模型中&#xff0c;自下而上第一个提供端到端服务的层次是&#xff08; &#xff09; A. 数据链路层 \qquad B. 传输层 \qquad C. 会话层 \qquad D.应用层 解析 本题主要考查 OSI 参考模型各层的核心功能、端到端服务的定义。 OSI 参考模型&am…

CentOS 7.9安装Nginx1.24.0时报 checking for LuaJIT 2.x ... not found

Nginx1.24编译时&#xff0c;报LuaJIT2.x错误&#xff0c; configuring additional modules adding module in /www/server/nginx/src/ngx_devel_kit ngx_devel_kit was configured adding module in /www/server/nginx/src/lua_nginx_module checking for LuaJIT 2.x ... not…

自制喜悦字贴

一、想法 据说&#xff0c;把“喜悦”两个字挂在家里显眼的地方&#xff0c;时常看到&#xff0c;就能心情愉悦。刚好最近在学习前端flex布局&#xff0c;用代码实现&#xff0c;导出图片&#xff0c;打印出来&#xff0c;帖在家里&#xff0c;非常nice。现在分享给大家。 二…

每日八股文6.3

每日八股-6.3 Mysql1.COUNT 作用于主键列和非主键列时&#xff0c;结果会有不同吗&#xff1f;2.MySQL 中的内连接&#xff08;INNER JOIN&#xff09;和外连接&#xff08;OUTER JOIN&#xff09;有什么主要的区别&#xff1f;3.能详细描述一下 MySQL 执行一条查询 SQL 语句的…

量化面试绿皮书:6. 烧绳子计时

文中内容仅限技术学习与代码实践参考&#xff0c;市场存在不确定性&#xff0c;技术分析需谨慎验证&#xff0c;不构成任何投资建议。 6. 烧绳子计时 你有两根绳子&#xff0c;每根绳子燃烧需要1小时。但是任何一根绳子在不同点都有不同的密度&#xff0c;所以不能保证绳子内不…

2-深度学习挖短线股1

选短线个股的流程 &#xff08;1&#xff09;数据预处理&#xff0c;根据短线个股筛选标准&#xff0c;给个股日线数据打标。 &#xff08;2&#xff09;模型训练&#xff0c;针对每只股票&#xff0c;训练得到分类模型。 &#xff08;3&#xff09;结果预测&#xff0c;根据训…

【数据分析】探索婴儿年龄变化对微生物群落(呼吸道病毒和细菌病原体)结构的影响

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍1. 混合效应逻辑回归模型2. 随机森林模型3. Maaslin2 分析加载R包数据下载导入数据数据预处理混合效应逻辑回归模型分析微生物群落结构随年龄的变化随机森林模型预测病原体定植Maas…

实战:子组件获取父组件订单信息

最佳实践建议 优先使用 props&#xff1a;适合父子组件直接通信&#xff0c;数据流向清晰复杂场景用 eventBus&#xff1a;跨组件通信推荐使用 mitt 库避免过度使用 $parent&#xff1a;会导致组件耦合度高&#xff0c;难以维护provide/inject 适用于跨层级&#xff1a;如主题…

Spring Security深度解析:构建企业级安全框架

Spring Security深度解析:构建企业级安全框架 本文将深入探讨Spring Security安全框架的核心原理、架构设计和实际应用,帮助开发者全面掌握企业级应用安全防护技术。 目录 Spring Security概述核心架构与原理认证机制详解授权机制详解核心组件分析配置与集成高级特性应用安全…

计算矩阵A和B的乘积

根据矩阵乘法规则&#xff0c;编程计算矩阵的乘积。函数fix_prod_ele()是基本方法编写&#xff0c;函数fix_prod_opt()是优化方法编写。 程序代码 #define N 3 #define M 4 typedef int fix_matrix1[N][M]; typedef int fix_matrix2[M][N]; int fix_prod_ele(f…

《Brief Bioinform》: 鼠脑单细胞与Stereo-seq数据整合算法评估

一、写在前面 基因捕获效率、分辨率一直是空间转录组细胞类型识别的拦路虎&#xff0c;许多算法能够整合单细胞(single-cell, sc)或单细胞核(single-nuclear, sn)数据与空间转录组数据&#xff0c;从而帮助空转数据的细胞类型注释。此前我们介绍过近年新出炉的Stereo-seq平台&…

camera功能真的那么难用吗

背景 Android开发工作过程中&#xff0c;经常需要用到camera相关能力&#xff0c;比如&#xff1a;人脸识别&#xff0c;ai识别&#xff0c;拍照预览&#xff0c;摄像头录制等等需求。都需要使用到camera&#xff0c;且需要拿到camera的预览数据。但是每次开发这块代码都比较繁…

USART 串口通信全解析:原理、结构与代码实战

文章目录 USARTUSART简介USART框图USART基本结构数据帧起始位侦测数据采样波特率发生器串口发送数据 主要代码串口接收数据与发送数据主要代码 USART USART简介 一、USART 的全称与基本定义 英文全称 USART&#xff1a;Universal Synchronous Asynchronous Receiver Transmi…

LeetCode 152. 乘积最大子数组 - 动态规划解法详解

文章目录 问题描述解题思路动态规划状态定义状态转移方程完整代码实现复杂度分析示例解析关键点说明总结问题描述 给定一个整数数组 nums,请找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组对应的乘积。 示例: 输入: [2,3,-2,4] 输出: 6 解…

Python: 操作 Excel折叠

💡Python 操作 Excel 折叠(分组)功能详解(openpyxl & xlsxwriter 双方案) 在处理 Excel 报表或数据分析时,我们常常希望通过 折叠(分组)功能 来提升表格的可读性和组织性。本文将详细介绍如何使用 Python 中的两个主流 Excel 操作库 —— openpyxl 和 xlsxwriter …

28、元组的遍历

const_cast 只能用于指针或引用类型&#xff0c;而不能用于基本类型如 int。 在的代码中&#xff0c;试图将 i 转换为 const_cast<int>(i)&#xff0c;这是不合法的。 可以使用模板函数来获取元组中的元素&#xff0c;而不是使用 const_cast。以下是修正后的代码&#x…

sendDefaultImpl call timeout(rocketmq)

rocketmq 连接异常 senddefaultimpl call timeout-腾讯云开发者社区-腾讯云 第一种情况&#xff1a; 修改broker 的配置如下&#xff0c;注意brokerIP1 这个配置必须有&#xff0c;不然 rocketmq-console 显示依然是内网地址 caused by: org.apache.rocketmq.remoting.excep…

【仿生机器人】仿生机器人智能架构:从感知到个性的完整设计

仿生机器人智能架构&#xff1a;从感知到个性的完整设计 仿生机器人不仅需要模拟人类的外表&#xff0c;更需要具备类人的认知、情感和个性特征。本研究提出了一个综合性的软件架构&#xff0c;实现了从环境感知到情感生成、从实时交互到人格塑造的完整智能系统。该架构突破了…