文章目录
- 一,如何存储数据的
- 二,一条sql语句的执行历程
-
- [Buffer Pool](#Buffer Pool)
- 日志
- [日志,磁盘,buffer pool流程](#日志,磁盘,buffer pool流程)
- 三,MVCC
- 四,其他问题
-
- [聚簇索引 VS 非聚簇索引](#聚簇索引 VS 非聚簇索引)
- 使用唯一索引会回表吗?
- MySQL引擎
- mysql中char和varchar的区别
- [limit执行逻辑 & 深度分页](#limit执行逻辑 & 深度分页)
- 为什么不推荐uuid做主键
- mysql中数据排序是怎么实现的
-
- 1,索引有序排序
- [2,filesort 文件排序](#2,filesort 文件排序)
一,如何存储数据的
表空间
1,表空间(Tablespace)
就是磁盘上的 .ibd 文件
一张表一个文件:如表user就是user.ibd
所有数据、索引都存在这里
2. 页(Page)
MySQL 读写的最小单位
默认大小 16KB
不管你查 1 行还是 10 行,都是一整页加载到内存
可以理解成:数据是一页一页存放的
.ibd 是一个大文件,页 (Page) 是这个文件里的最小存储单元。
关系就是:文件 → 由无数个 16KB 的页 组成。
3. 区(Extent)
由 64 个连续页组成,用来管理空间,不用深究
4. 段(Segment)
比如:索引段、数据段。若干个区和零散的页组成段。
段(Segment) = 用来存放某一类数据的 "区域"
一个
.ibd文件里,会分成很多个段,段下面再分**区 **。

索引
innodb引擎有两大类。聚簇索引和普通索引。普通索引也叫二级索引。
一级索引 = 聚簇索引 = 主键索引。
特点:
- 一张表只能有一个
- 叶子节点存整行完整数据
- 默认就是你的主键
PRIMARY KEY - 数据物理存储顺序就按一级索引排
普通索引有唯一索引,联合索引(遵循最左前缀原则)等等
聚簇索引的存储
在Innodb中索引即数据,在创建表时会默认生成聚簇(主键)索引,如果创建表时未设置主键,则会使用记录的隐藏列作为主键
聚簇索引的特点是以主键排序并拥有完整的记录
在叶子节点中记录以主键升序维护成单向链表,非叶子节点中记录则是以下层目录页中最小主键升序维护成单向链表
为了方便范围查找同级节点之间会维护成双向链表

当查询时会从根节点(非叶子节点)一步一步查询到叶子节点。
对于叶子节点来说,页中的记录维护成单向链表,在一个页中搜索记录的时间复杂度为O(n),当数据量较大时只能进行遍历。那如何减少查询时间呢?就有了"组"的概念。
在一个页中,由于页内记录是有序的,为了加快查找速度将页内的记录分为多个组,将每个组中的最大记录维护成一个升序列表
图中不同颜色的记录为不同的组,每个组的最大值维护成升序列表(infimum,2,4,6,supermum)

页内默认有最小的记录infimum和最大的记录supermum,其中infimum记录单独为一组,supermum可以和其他记录为一组(它们的加入是为了方便加间隙锁,防止幻读)
这样在进行页内查找时可以使用二分法进行查找,将时间复杂度降低为O(log n)
二级索引的存储
为表中某个列建立索引时,可以称这个索引为二级索引
聚簇索引与二级索引较大的区别为:聚簇索引存储完整的记录,而二级索引上的记录只存储索引列、主键
建立age、student_name的联合索引(二级索引)
二级索引中记录则只存储age、student_name、id的信息,并以age、student_name、id的顺序升序排序
当age相等时,根据student_name升序排序;当student_name相等时,再根据id升序排序
如果使用二级索引时要获取完整数据还需要回表查询聚簇索引,比如使用二级索引时还要获取info列则需要回表查询聚簇索引
什么是回表?
回表 是 MySQL InnoDB 存储引擎中,通过二级索引(普通索引)查询数据时,需要再查一次主键索引才能拿到完整数据的过程。
二,一条sql语句的执行历程
MySQL 执行过程详解:从 SQL 语句到结果返回的完整旅程-腾讯云开发者社区-腾讯云

Buffer Pool
Buffer Pool 是 InnoDB 存储引擎的核心内存组件,本质是一块用来缓存表数据页和索引页的内存区域,作用是大幅减少磁盘 I/O,提升读写性能。
数据加载流程
- 当 SQL 查询 / 修改数据时,InnoDB 会先检查目标数据页是否在 Buffer Pool 中。
- 如果不在,就从磁盘文件(
.ibd)中读取 16KB 的数据页,加载到 Buffer Pool 中。 - 加载进来的数据页会被加上锁(如共享锁 / 排他锁),保证并发访问的安全性。
数据读写流程
- 所有数据的读、写操作,都直接在内存中的 Buffer Pool 里完成,不会每次都直接操作磁盘。
- 修改后的数据页会被标记为 "脏页",后续由后台线程异步刷回磁盘。
日志
Redo Log(重做日志):循环写入,记录物理页修改,用于崩溃恢复。
Undo Log(回滚日志):记录修改前的数据版本,用于事务回滚 和 MVCC。
日志,磁盘,buffer pool流程
当执行一条 SQL:
sql
UPDATE user SET age=20 WHERE id=1;
第 1 步:去 BufferPool 找数据
- 看 BufferPool(内存缓冲池) 里有没有
id=1这行数据 - 有 → 直接用
- 没有 → 从磁盘数据文件(ibd) 加载到 BufferPool
第 2 步:写 UndoLog (日志)
目的:万一回滚,能恢复旧数据
- 把旧值
age=10写入 undo log - 这是内存日志,也会刷磁盘
第 3 步:修改数据(只改内存)
- 在 BufferPool 里 把
age改成 20 - 此时:内存已改,磁盘没变。
- 这页数据变成 "脏页"(dirty page)还没写 Redo Log,崩溃重启后等于没修改。
第 4 步:写 RedoLog (日志)
目的:崩溃了也能恢复,保证事务不丢
- 记录:
id=1 把 age 改成 20 - 先写 redo log buffer(内存)
- 再根据策略刷到 redo log 磁盘文件(ib_logfile)
第 5 步:写 Binlog (日志,MySQL 特有)
- 记录逻辑日志:
UPDATE user SET age=20 WHERE id=1 - 用于主从复制、数据恢复
第 6 步:两阶段提交(保证数据一致)
-
Prepare 阶段:redo log 标记为 prepare
-
Commit 阶段:binlog 写入成功,redo log 标记为 commit
第 7 步:最终刷脏页到磁盘(异步)
数据库空闲时或 BufferPool满了,才把 BufferPool 里的脏页刷到 磁盘 ibd 文件
脏页是指内存中修改过、但还没刷回磁盘的数据页。MySQL 采用了 WAL(Write-Ahead Logging,预写日志)机制:
所有数据修改,必须先写入 Redo Log,再写入数据页。
三,MVCC
Purge 是 InnoDB 的后台 "垃圾回收" 操作:异步清理不再需要的 undo log 和旧数据版本,回收空间,让 MVCC 能安全运行。
这里的删除操作只是设置一下老记录的 DELETED_BIT,并不真正将过时的记录删除,为了节省磁盘空间,InnoDB有专门的purge线程来清理 DELETED_BIT 为true的记录。
MVCC,全称 Multi-Version Concurrency Control,即多版本并发控制。多版本指的是数据库中同时存在多个版本的数据
MVCC的目的主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突。
基础概念之视图:
视图就是一个 "虚拟表"。它不存真实数据,只存一条 SQL 查询语句。查询视图的时候,它就动态执行这条 SQL,把结果返回给你。
优点:简化查询,安全、隐藏字段,统一逻辑,不占存储空间。
视图里的数据可以修改(直接修改原表数据),但有严格要求。比如:只来自一张表(不能多表 join),包含主键 / 唯一键等等。
1,什么是快照读?
每个事务在开始时会创建一个一致性视图(Consistent View),该视图反映了事务开始时刻数据库的快照。这个一致性视图会记录当前事务开始时已经提交的数据版本。快照读读取的数据就是这个。
2,什么是当前读?
读取时要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。MySQL提供了两种实现当前读的机制:一致性读(Consistent Read)和锁定读(Locking Read)。
快照读的前提是隔离级别不是串行级别,在串行级别下,事务之间完全串行执行,快照读会退化为当前读。
**ERIALIZABLE 要求:所有事务必须像排队一样串行执行,绝对不能并发。**为了实现这种 "绝对安全",MySQL 在这个级别下:对所有普通 select 自动加上了
LOCK IN SHARE MODE(共享锁)
区别:快照读是在第一次查询的时候存的快照,相当于RR,事务过程中度的数据一样,可能是旧数据,这个旧数据可能是本事务修改的,也可能是其他事务修改的。而当前读可以读取本事务修改的最新数据,如果在本事务过程中修改了数据可以读取,且其他事务无法修改。
3,Read View
一致性视图,全称 Read View ,是用来判断版本链中的哪个版本对当前事务是可见的。就是决定当前事务能看见哪个版本的数据(undo log 里的哪个历史版本)。
ReadView 是【每个事务自己的私有快照】,只为【当前这个事务】服务。
RC(读已提交)和RR(可重复读)的readview的区别:
- RC:每次 SELECT 数据前都生成一个ReadView。
- RR:只在第一次读取数据时生成一个ReadView,后面会复用第一次生成的。
四,其他问题
聚簇索引 VS 非聚簇索引
| 对比项 | 聚簇索引(Clustered Index) | 非聚簇索引(Secondary Index) |
|---|---|---|
| 数量 | 一张表有且只有 1 个 | 一张表可以有多个 |
| 叶子节点内容 | 完整的一行数据(所有字段) | 索引列的值 + 主键值 |
| B+ 树结构 | 一棵完整的 B+ 树,树的叶子节点就是数据页 | 独立的 B+ 树,叶子节点是索引页。(索引页 = 存放 B+ 树 非叶子节点 的页) |
| 查询过程 | 直接定位到叶子节点,拿到完整数据 | 先查索引树拿到主键,再回表查聚簇索引树 |
| IO 次数 | 1~3 次磁盘 IO(树高决定) | 至少 2 次(索引树 + 聚簇索引树) |
使用唯一索引会回表吗?
唯一索引和会不会回表没有直接和关系,如果查询的列被索引覆盖那就不会回表,否则回表。
MySQL引擎
1)InnoDB
- 事务安全 :支持
COMMIT/ROLLBACK,适合支付、订单。 - 行级锁 :改一行只锁一行,高并发读写强MySQL。
- 聚簇索引:主键查询极快;二级索引存主键,可能回表。
- MVCC:读不阻塞写,写不阻塞读。
- 有 redo/undo log:宕机能恢复。
- 缺点:空间占用略大,配置稍复杂。
2)MyISAM
- 不支持事务:写操作不能回滚。
- 表级锁:一写就锁全表,并发差。
- 查询快、占用小:适合纯读、日志、报表。
- 索引和数据分开 :
.MYI索引、.MYD数据。 - 崩溃易丢数据:无事务日志。
3)Memory(内存引擎)
- 数据全在内存:读写极快。
- 重启数据消失:不能存重要数据。
- 不支持事务。
- 适合:临时表、会话缓存、计数器、高频小表。
mysql中char和varchar的区别
CHAR(M)
- 固定长度字符串类型
- 定义时必须指定长度
M(0~255) - 存入数据不足长度时,自动用空格填充;查询时自动去掉填充的空格
VARCHAR(M)
- 可变长度字符串类型
- 定义时必须指定最大长度
M - 只存储实际输入的字符,不填充空格
CHAR 更快
因为长度固定,MySQL 直接定位数据,不需要计算长度,查询、更新速度更快
VARCHAR 更省空间
只存实际数据,适合长度不固定的文本,空间利用率更高
limit执行逻辑 & 深度分页
MySQL 执行 LIMIT 100000,10 时:先扫描前面 100000 条数据,再扔掉这 100000 条,最后只返回后面 10 条。相当于白扫了 10 万条无用数据,数据越大越慢。如果是非覆盖索引还需要回表。
核心问题:
- 全量扫描:MySQL 需先读取
offset + limit条数据(如 1,000,010 行),再丢弃前 1,000,000 条,有效工作量仅 0.001%。 - 高资源消耗:大量 I/O 操作、内存占用(排序缓存)、CPU 飙升。
- 性能衰减:千万级数据下,查询耗时从毫秒级增至秒级甚至分钟级。
**解决方案:**游标分页
用上一页末尾记录的 ID 或时间戳作为起点,避免 OFFSET。
sql
-- 第一页
SELECT * FROM orders ORDER BY id DESC LIMIT 10;
-- 下一页(假设上一页最后ID=100)
SELECT * FROM orders WHERE id < 100 ORDER BY id DESC LIMIT 10;
为什么不推荐uuid做主键
《阿里巴巴 Java 开发手册》MySQL 建表规约:主键 id 必须为 unsigned bigint、单表自增、步长为 1。明确反对用 UUID、MD5、字符串 等无序 / 长文本当主键。
InnoDB 主键是聚簇索引 :数据按主键顺序物理存储,B + 树节点有序排列。
1,自增 ID:新数据永远追加到索引末尾,无页分裂,插入极快。
UUID:完全随机、无序,新 ID 可能插在索引中间 / 开头,触发:
- 页分裂:数据页满了就分裂、重平衡,大量随机 I/O;
- 索引碎片:物理页离散,查询变慢;
- 高并发下插入抖动明显。
2,长度大 → 索引膨胀、缓存命中率低
UUID :128 位(16 字节),字符串 36 字符(含 -);
bigint :64 位(8 字节),仅为 UUID 的 1/2。
3,字符串比较慢 → 查询效率低
UUID 是字符串,对比时需逐字符遍历 ;bigint 是数字,直接二进制比较,速度差一个量级。
4,分布式场景也不推荐 → 有更好替代
UUID 虽全局唯一,但:
无序问题无解;
信息不安全:部分 UUID 含 MAC 地址,可被追溯;
阿里推荐用雪花算法(Snowflake) :64 位、趋势递增、全局唯一、长度与 bigint 一致。
mysql中数据排序是怎么实现的
1,索引有序排序
eg:
sql
select * from user order by id;
id 是主键(聚簇索引),索引 B+ 树本来就按 id 从小到大排好序了,MySQL 直接按顺序遍历索引 → 不需要做任何排序操作,这就是最优排序,几乎无消耗。
如果用的是二级索引,且是覆盖索引,同样不需要排序表,需要回表,但速度依然很快
2,filesort 文件排序
分 3 步:
步骤 1:取出需要的数据
MySQL 先把 where 匹配到的所有行取出来。
步骤 2:放到排序区(内存 or 磁盘)
- 数据小 → 用内存快速排序(快)
- 数据大 → 内存放不下,利用磁盘临时文件归并排序(慢)
步骤 3:排完后返回结果
排序完成后,再按 limit 取数据返回。