浅析MySQL中的数据存储结构

本篇文章主要从InnoDB数据存储结构的角度分析,在何种情况下,SQL查询效率会降低。经常在网上看到一些文章在吐槽,数据量大的情况下,查询效率会降低很多。表关联的多的时候,查询效率会降低。单表数据量不要超过百万等等。

浅析MySQL中的数据存储结构

数据库版本: 8.0引擎:InnoDB参考资料:掘金小册 《从根上理解Mysql》,有时间的建议亲自看一下。

样例表:

CREATE TABLE `hospital_info` (  `pk_id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',  `id` varchar(36)  NOT NULL COMMENT '外键',  `hospital_code` varchar(36)  NOT NULL COMMENT '医院编码',  `hospital_name` varchar(36)  NOT NULL COMMENT '医院名称',  `is_deleted` tinyint DEFAULT NULL COMMENT '是否删除 0否 1是',  `gmt_created` datetime DEFAULT NULL COMMENT '创建时间',  `gmt_modified` datetime DEFAULT NULL COMMENT 'gmt_modified',  `gmt_deleted` datetime(3) DEFAULT '9999-12-31 23:59:59.000' COMMENT '删除时间',  PRIMARY KEY (`pk_id`),  KEY `hospital_code` (`hospital_code`)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='医院信息';

InnoDB 行格式

从一行数据开始看起,先了解一下单行数据的存储格式。目前行格式有4种,分别是CompactRedundantDynamicCompressed行格式。在创建表的时候一般不需要刻意指定,5.7以上的版本会默认Dynamic。每种行格式大同小异,这里以Compact作为一个样例,简单的了解一下,每行数据是如何记录的。image.png

如上图所示。分为“额外信息”和“真实数据”两个部分。

变长字段列表

这个比较有意思,一般在定义字段的时候都需要指定字段的类型长度

比如:样例表中的hospital_code字段定义VARCHAR(36)。在实际使用中hospital_code字段长度只用了32位。

那剩下的4个字符长度会怎么办?若强行填充空字符,岂不是白白浪费4个字符的内存。若不填充,怎么判断当前字段到底保存了多少个字符?占用多少内存?

此时,变长字段列表就会按字段反序,用1~2个字节,记录每个变长字段实际的长度。这样可以有效的利用内存空间。

与之类似的字段:VARBINARY、各种TEXT类型,各种BLOB类型。

相对的也存在“定长字段”,比如:CHAR(10),该类型的字段,在初始化的时候就会默认占用指定字符长度的空间,若不够则填充空字符,因此对空间上是比较浪费的,一般建议按需设置长度。

当然“变长字段列表”不是必定存在的,若定义的字段类型没有“变长字段”则不会有。

拓展:对于TEXT或BLOB类型的字段,长度很可能一页无法存下,这时会将大部分数据记录在其他页中,在当前记录中保留下一页数据的地址。

NULL值列表

在实际保存数据的时候,某些列可能存储的是NULL值,如果这些值都记录在真实的数据中,则会浪费存储空间。在Compact格式中,会把这些值为NULL的列统一管理,存储到NULL值列表中。

若一行数据中,没有字段为NULL则不会产生此列。

存储的方式也比较有意思,是二进制方式倒序记录

以样例表来分析,表中存在is_deletedgmt_createdgmt_modified三个字段可能为空。假设在一条记录中gmt_created、gmt_modified都为空,那对应到NULL值列表中应该是下面的样子。

image.png

拓展:Mysql是支持二进制数据存储的,充分利用,可以减少很大的存储空间。

记录头信息

记录头信息由固定的5个字符组成,即40个二进制位长度。

image.png

先作为一个了解,这里有一个比较有意思标识:delete_mask用过redis的都知道,redis的中被删除的数据不会被立刻清除,相同的mysql中也一样,被删除的数据不会立刻被清理,因为清理的过程会引发IO操作,这是很影响效率的。被删除的数据会组成一个链表,想当与一个可复用的空间。

记录真实数据数据

这个其实没啥好说的,就是记录真实的非NULL数据。

有一个网上经常能看到的问题:若没有设置主键会怎样?

InnoDB下,主键是一条记录的唯一标识,如果用户没有指定,mysql会从Unique(唯一)键中选取一个作为主键,如果没有Unique键,则会添加一个名为row_id隐藏列,作为主键。

此外还会添加添加 transaction_id(事务ID)roll_pointer(回滚指针) 这两个列。

小结

4种行格式大同小异,就不一一介绍了,都分为“额外信息”和“真实数据”两个部分。区别主要在与“额外信息”记录的内容,以及变长字段的保存上的些许不同。

InnoDB数据页

数据页的概念,相信已经耳熟能详了。它是InnoDB管理存储空间的基本单位,单页大小一般是16KB。根据不同的目的设计了许多不同类型的页,如:存放表空间头部信息的页,存放Insert Buffer信息的页,存放INODE信息的页,存放undo日志信息的页等等。

页空间划分如下:image.png

总共7个组成部分,大致描述一下7个部分。

image.png

其中File headerPage header中的属性非常多,这里不一一介绍,只要知道这两个地方记录的一些属性,比如:页号,上一页和下一页的页号,页的类型,以及页的内存占用等等。这里说一下,页与页之间是双向链表进行连接的。数据记录是单项链表

File Trailer是校验页数据完整性的,当页数据从内存重新写入磁盘的时候需要校验,防止数据页损坏。

重点关注下User Records(已用空间)Free Space(剩余空间),这里是保存真实的数据记录。

此外 InfimumSupremum,分别标识最小记录和最大记录。即一个页产生的时候,就默认包含这两条记录,不过不用担心这两条记录只是作为数据链表的头和尾,不影响真实数据。

综上,记录在页中的存储如下:

即构数智人 即构数智人

即构数智人是由即构科技推出的AI虚拟数字人视频创作平台,支持数字人形象定制、短视频创作、数字人直播等。

即构数智人 36 查看详情 即构数智人

image.png简单的来说,就是Free Space到User Records的转化,当Free Space耗尽时则视为数据页已经满了。

到此,数据已经写入了数据页中。那该怎么取出呢?上面知道了数据记录是单项链表组成的,难道要从Infimum(最小)记录开始沿着链表遍历吗?

显然,mysql的开发大佬不可能这么蠢,否则我上我也行,哈哈。

这里就要提到 Page Directory(页目录)了。在页中,对数据进行了分组,每组最后一条记录的地址偏移量单独提取出来按顺序存储到靠近页尾的“页目录”中,页目录中的这些地址偏移量被称为“”,此外最后一条记录头部(n_owned)还要保存所在分组中有多少条记录。

页目录是由一个个的槽组成的。整体结构图如下:image.png

有了目录之后,查询就比较简单了。可以使用二分法进行快查。上图中,知道最小槽为0,最大为4.举个栗子:

假设要查询主键记录为6的数据。

1)计算中间槽位置即(0+4)/ 2 = 2。取出槽对应的记录主键为8,因为8>6。

2)同理,将最大的槽设置为2,即(0+2)/2 =1,槽1对应的主键为4,因为 4 < 6, 所以可以确定数据就在槽2中。

为了方便后续的描述,将页的数据形式简化为如下图所示的样子。image.png

B+树索引

不妨思考一个问题,前面说了。数据页之间使用的是双向链表链接的,大致如下图所示:image.png上图可以看能出页号并非连续的也并不一定是连续的内存空间(记住这句话后面会说到)

假设每页能存放3条记录,现在有10w条记录需要保存,则需要3w多个数据页,此时会面对和单页数据过多一样的查询问题,总不能逐个遍历吧。此时也需要一个能快速快查询的目录,这个目录就是“索引”。

在上图所示的数据页基础上,可以形成如下的索引结构:image.png这种就是常说的聚簇索引,叶子即数据。这里要注意的一点,“页30”中存放的是主键以及其所在的页号。如果说单个索引页满了,则会进行分裂。产生如下所示的树形结构image.png不过上图为了标识方便,是不完全准确的。应该是先产生一个根节点,当根节点满了,则会进行分裂。根节点则记录分裂后的索引页信息。

简单的来说就跟树木成长一样,先从根再到树干、树枝、树叶等。

二级索引与聚簇索引的思路是一样的,差别在于二级索引的叶子节点不是真实数据,而是数据的主键。需要进行回表操作才能获取真实数据。

表空间

到目前为止,已经知道单条数据的存储结构,以及最小的存储数据单元页。数据页之间通过双向链表进行连接,并且数据页之间是不一定连续的。

此时,产生了一个问题,同一个表的记录,如果所在的页在内存地址上相距过远怎么办?设想一下为了找3个人,他们分别再北京、纽约、伦敦。你要挨个去找,中间要浪费大量的时间在旅途中。如果把他们聚集在一个国家,甚至一个城市,那就要快很多。

于是的概念诞生了。区是由连续的64个组成,默认情况下一个区占用1M的内存。在申请内存的时候,一次性占用1M的空间,其中的数据页都是相邻的,一定程度上解决了随机IO的问题。

在区的基础上,为了更有效的提升查询效率,将B+树的叶子节点和非叶子节点记录在不同的区中,这些区的集合被成为“段(segment)”。在此概念下,插入第一条记录,就需要申请2个区空间,一个聚簇索引根节点,一个数据页,这一次就需要申请2M的空间!啥也没干呢,2M空间就没了,这合理吗?显然,这很不合理。

因此又搞出一个”碎片区“的概念。碎片区直属于表空间,不属于任何一个段。分配内存的流程转变成:

1)刚开始插入数据时,从碎片区以单个页面来分配存储空间。

2)当某个段已经占用了32个碎片区页面后,就会以完整的区来分配空间。

表空间还分为:系统表空间独立表空间,此外还有区的XDES Entry数据结构。内容过多且复杂,需要了解的可以去看原书。

思考

1)索引越多越好吗?多了会有 什么影响?

那肯定不是越多越好,上面可以知道,索引的记录也是需要内存损耗的。每个索引都会对应一个B+树,每个树有需要2个段分别记录叶子节点和非叶子节点。这么下来会带来很多内存的浪费。仅仅是这样的话也不是不能接受,毕竟索引本身的意义就是用空间换时间。但我们要知道,数据的增删改,会导致索引的变化,需要索引重新分配节点,以及页内存的回收分配。这些都是IO操作,若索引过多,势必导致性能的降低。

因此合理的利用联合索引,可以解决单个索引过多的问题。此外索引有长度限制,过长的字段不适合作为索引。

2)索引为何查询效率这么高?

这个其实属于算法问题,以聚簇索引为例,假设非叶子节点的索引页,每个能记录1000条数据,叶子节点每个能记录500条数据,一个3层的B+树(不算根节点),能存放10001000500条记录。一个3层结构的索引能存放这么多记录,每次只需几次查询就能定位数据,效率自然也就高了。

实际上单个索引页所能记录的数据要比这大的多。

同样的这里可以思考一个问题,若叶子节点中的单条数据非常大,大到一个数据页只能存放3条记录,这时B+树的深度就会增加,因此合理的减少表中单条记录的大小,也是一种优化。

3)数据量大,sql会执行缓慢?

其实这个问题真的很想吐槽,动不动就百万数据查询效率xx秒,太慢了。不否认mysql的性能的确弱于一些数据库,但是百万的数据量就慢的,想想自己的SQL和表结构设计是否合理。别说百万级,就是千万级的也能实现毫秒级的查询。只谈数量都是扯淡,要实际看看锁占用的内存大小,若你的表中有上百个字段,或者存在字符超长的字段。那么神仙也救不了你。

总结

文章主要是介绍MySql数据结构的概念,大部分内容都来自于《从根上理解Mysql》一书。做了很多简化,可以作为基础了解一些概念。

如有错漏,感谢指正。

【相关推荐:mysql视频教程】

以上就是浅析MySQL中的数据存储结构的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/350985.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月5日 22:17:13
下一篇 2025年11月5日 22:21:43

相关推荐

  • 如何用Python源码开发追剧提醒系统 Python源码定时任务与接口集成

    要开发python追剧提醒系统,关键步骤如下:1.选择数据库存储信息,小型项目用sqlite,大型用mysql;2.调用视频源api或使用爬虫获取更新数据,注意频率限制和合规性;3.使用schedule或apscheduler实现定时任务,前者适合简单任务,后者支持复杂调度;4.通过邮件、短信或微信…

    2025年12月14日 好文分享
    000
  • 如何使用Python构建注塑产品的尺寸异常检测?

    构建注塑产品尺寸异常检测系统,首先要明确答案:通过python构建一套从数据采集到异常识别再到预警反馈的自动化系统,能够高效识别注塑产品尺寸异常。具体步骤包括:①从mes系统、csv/excel、传感器等来源采集数据,使用pandas进行整合;②清洗数据,处理缺失值与异常值,进行标准化;③结合工艺知…

    2025年12月14日 好文分享
    000
  • Python如何操作MongoDB?NoSQL数据库实战

    python操作mongodb的核心依赖pymongo库,其核心步骤包括:1. 安装pymongo;2. 建立与mongodb的连接;3. 选择数据库和集合;4. 执行增删改查操作;5. 使用聚合和批量操作提升性能;6. 关闭连接。mongodb作为文档型数据库,与传统关系型数据库相比,具有灵活的无…

    2025年12月14日 好文分享
    000
  • 连接 MySQL 5.1 数据库的 Python 教程

    本文档旨在指导开发者如何使用 Python 连接到 MySQL 5.1 数据库。由于 MySQL 5.1 较为古老,现代的 MySQL 连接器可能存在兼容性问题。本文将介绍如何使用 mysql-connector-python 驱动,并配置相应的参数,以成功建立连接。同时,本文也强烈建议升级 MyS…

    2025年12月14日
    000
  • Python连接MySQL 5.1:克服旧版认证与字符集兼容性挑战

    本教程详细阐述了如何使用Python 3和mysql.connector库成功连接到老旧的MySQL 5.1数据库。文章重点介绍了解决旧版认证协议和字符集兼容性问题的关键配置,特别是use_pure=True和charset=’utf8’的重要性,并提供了可运行的代码示例。同…

    2025年12月14日
    000
  • Python如何做自动化爬虫?Scrapy框架指南

    scrapy是当前最成熟、功能最强大的python自动化爬虫框架,其核心优势在于提供从请求发起到数据存储的完整解决方案。1. scrapy基于异步io实现高并发,提升爬取效率;2. 其模块化设计支持清晰架构与高度扩展性;3. 中间件系统灵活应对反爬策略;4. 内置item与pipeline实现数据结…

    2025年12月14日 好文分享
    000
  • 如何实现Python与数据库的批量数据交互?高效IO方案

    优化python数据库操作的核心在于减少交互次数和高效利用io。1.使用批量操作避免单条sql循环执行,如psycopg2的execute_batch或pymysql的executemany;2.通过连接池管理连接,减少频繁创建销毁连接的开销,并根据数据库性能、并发量合理设置连接池大小;3.采用异步…

    2025年12月14日 好文分享
    000
  • Python如何实现网络爬虫?Scrapy框架教程

    要实现网络爬虫,python 中最常用、功能强大的框架之一是 scrapy。1. 安装 scrapy 并创建项目:使用 pip install scrapy 安装,并通过 scrapy startproject myproject 创建项目;2. 编写第一个爬虫:在 spiders 目录下新建 py…

    2025年12月14日 好文分享
    000
  • 怎样用Python构建端到端异常检测流水线?完整架构

    数据预处理在异常检测中扮演提升数据质量、统一数据尺度、提取有效信息和适配模型输入四大核心角色。1. 提升数据质量:处理缺失值、异常值和噪声,避免模型学习错误模式;2. 统一数据尺度:通过标准化或归一化消除特征量纲差异,确保模型公平对待所有特征;3. 提取有效信息:进行特征工程,如创建滞后特征、滚动统…

    2025年12月14日 好文分享
    000
  • Python中如何实现并发编程?asyncio协程详解

    asyncio和协程是python中处理i/o密集型任务的高效并发方案,其核心在于通过事件循环实现单线程内的合作式多任务调度。1. 协程由async def定义,通过await暂停执行并释放控制权给事件循环;2. 事件循环负责监控和调度就绪的协程,避免阻塞;3. 使用asyncio.run()启动事…

    2025年12月14日 好文分享
    000
  • Python中如何实现定时任务?APScheduler详细配置

    实现python定时任务的核心工具是apscheduler,其使用步骤如下:1. 安装apscheduler;2. 根据应用场景选择调度器,如backgroundscheduler适合后台运行;3. 配置调度器,包括时区、任务存储、执行器及任务默认属性;4. 使用add_job()方法添加任务,并指…

    2025年12月14日 好文分享
    000
  • Python如何连接MySQL数据库?PyMySQL详细使用教程

    %ignore_a_1%是python连接mysql数据库的首选工具。1.安装pymysql:使用pip install pymysql命令安装;2.连接数据库:通过pymysql.connect()方法建立连接,并使用cursor执行sql语句;3.使用连接池:通过dbutils.pooled_d…

    2025年12月14日 好文分享
    000
  • 如何用Python制作爬虫?Scrapy框架入门

    scrapy是python爬虫开发的利器,因其功能完备、高效稳定且模块化设计而广受欢迎。它封装了异步请求处理、数据提取工具(如css选择器和xpath)、以及强大的中间件机制(包括下载器和spider中间件),极大简化了并发控制、异常处理与反爬应对。其结构化项目布局提升开发效率,通过定义item明确…

    2025年12月14日 好文分享
    000
  • 怎样用Python操作SQLite?轻量数据库使用指南

    python操作sqlite数据库的核心是使用内置的sqlite3模块,其流程包括:1. 导入模块;2. 使用sqlite3.connect()建立数据库连接(可为文件或内存);3. 创建游标对象;4. 执行sql命令进行增删改查;5. 通过commit()提交更改或rollback()回滚事务;6…

    2025年12月14日 好文分享
    000
  • 怎样用Python开发数据管道?ETL流程实现

    用python开发数据管道的关键在于理解etl流程并选择合适的工具。1. etl流程包括三个阶段:extract(从数据库、api等来源抽取数据)、transform(清洗、格式化、计算字段等)、load(将数据写入目标存储)。2. 常用工具包括pandas(处理中小型数据)、sqlalchemy(…

    2025年12月14日 好文分享
    000
  • 如何使用Python操作HBase?分布式数据库

    要使用python操作hbase,主要依赖thrift服务和happybase库。1. 安装并启用hbase thrift服务,使用命令安装thrift并启动hbase thrift;2. 使用happybase连接hbase,通过pip安装后可创建表、插入数据及查询;3. 处理中文或编码问题,写入…

    2025年12月14日 好文分享
    000
  • 怎样用Python操作JSON文件?读写性能优化方案

    python操作json文件的核心是使用内置json模块进行序列化与反序列化,读写性能受文件大小和应用场景影响。1. 小文件处理通常无需优化,直接使用json.load()和json.dump()即可;2. 大文件需采用流式解析库如ijson,按需读取以降低内存占用;3. 写入大量数据时避免格式化、…

    2025年12月14日 好文分享
    000
  • Python csv.writer 写入数据时额外引号问题的解析与解决方案

    本文旨在解决使用 Python csv 模块的 csv.writer 写入数据时,因数据源结构不当导致输出字段被额外引号包裹的问题。当从数据库(如 MySQL)获取的数据集每行是一个包含预先逗号分隔字符串的单元素元组时,csv.writer 会将其视为单个字段并添加引号。教程将详细分析问题成因,并提…

    2025年12月14日
    000
  • 如何用Python构建数据监控—异常检测报警系统

    1.明确监控对象与异常定义,如数据来源、监控频率及异常判断标准;2.采集并预处理数据,包括获取数据源和清洗格式化;3.实现异常检测逻辑,可采用统计方法或时间序列模型;4.设置报警通知机制,如邮件、企业微信等。系统构建流程为:确定监控目标、采集清洗数据、应用检测算法、触发通知,同时需确保数据源稳定、规…

    2025年12月14日 好文分享
    000
  • Python怎样操作MySQL数据库?PyMySQL连接方法

    pymysql连接mysql数据库的核心步骤包括导入库、建立连接、创建游标、执行sql、事务处理及关闭连接。1. 导入pymysql模块;2. 使用pymysql.connect()建立连接,传入数据库配置参数;3. 通过with conn.cursor()创建并自动管理游标;4. 使用cursor…

    2025年12月14日 好文分享
    000

发表回复

登录后才能评论
关注微信