MySQL一行记录是如何存储的?
以例示意CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(20), age INT, phone VARCHAR(20), address VARCHAR(100) ) ENGINEInnoDB;插入一行INSERT INTO users VALUES (1, Tom, 18, 13800000000, Shanghai);这行在 InnoDB里不是简单存成1 Tom 18 13800000000 Shanghai而是额外存一些管理信息变长字段长度列表 name 长度、phone 长度、address 长度 NULL 值列表 哪些字段是 NULL 记录头信息 这条记录的状态、下一条记录的位置等 隐藏列 事务 ID trx_id 回滚指针 roll_pointer 可能还有隐藏 row_id 真实数据 id 1 name Tom age 18 phone 13800000000 address Shanghai为什么有“变长字段长度列表”因为像这些字段因为VARCHAR所修饰的变量长度不确定本例中name、phone、address均为变长字段不是固定长度比如 nameTom只需要三个字符的空间但nameAlexander需要更多空间。所以 InnoDB 要在记录里额外存每个变长字段实际用了多少字节。官方文档也说明对于非 NULL 的变长字段记录头会保存该列长度长度字段通常占 1 或 2 字节。为什么有NULL值列表如果某个字段允许为NULLInnoDB 不需要真的存一个字符串NULL。他会用类似位图(考过408的兄弟应该不会陌生)的方式标记即类似于该字段为null时标1否则标0。例如INSERT INTO users(id, name, age, phone, address) VALUES (2, Jack, 20, NULL, NULL);这一行里phone和address是NULLInnoDB 会在 NULL 位图中标记它们。对于COMPACT行格式可为 NULL 的列会用位向量记录N 个可为 NULL 的列大约占CEILING(N/8)字节。什么是记录头信息 record header记录头信息是 InnoDB 管理这一行用的不是你表里定义的字段。内容大概记录这条记录是否被删除下一条记录的位置记录类型堆中的位置字段数量相关信息可以理解成链表和页内管理用的“元信息”。你写的 SQL 看不到这些内容但 InnoDB 内部需要靠它们组织数据页里的记录。什么是隐藏列InnoDB 的聚簇索引记录除了用户定义的列还会额外存一些隐藏信息。官方文档说明聚簇索引记录包含所有用户定义列另外还有 6 字节事务 ID 字段和 7 字节回滚指针字段如果表没有主键还会有 6 字节 row ID 字段。即trx_id 事务 IDroll_pointer 回滚指针row_id 没有主键时才可能有trx_id表示最后修改这行记录的事务 ID。它和事务隔离、MVCC 有关。roll_pointer指向 undo log用来找到这行记录的历史版本。比如你在事务中更新一行数据UPDATE users SET age 19 WHERE id 1;InnoDB需要能找到旧版本 age 18。有同学问了找到旧版本数据有啥用呢这里主要有两个功能1、保证原子性Atomicity提供事务回滚Rollback的能力。当你执行UPDATE users SET age 19 WHERE id 1;时只要你还没有敲下COMMIT提交事务这个操作就是可以撤销的。如果此时你敲了ROLLBACK命令或者你的代码突然抛出异常崩溃了。MySQL可以通过roll_pointer知道旧数据长什么样把18重新盖回到表里即要么全部发生要么全部没有发生。2、为了并发时的“时空穿越”假设现在是双11高峰期事务 A正在执行你的 SQL把age改成了19但还没提交未提交的脏数据。事务 B这时恰好进来了执行了一句SELECT age FROM users WHERE id 1;。为了防止脏读可以采用加锁排队的做法因为事务 A 正在改这行数据所以这行数据被锁住了。事务 B 只能傻乎乎地干等着直到事务 A 提交了才能读。这叫“读写冲突”高并发下性能极差为了提高并发性InnoDB 根本不让事务 B 等事务 B 发现当前最新的数据age19是被一个还没提交的事务修改的它无权查看。于是事务 B 就会顺着roll_pointer指针像坐时光机一样回到过去从undo_log里把历史版本age18(只要还没commit就不是读脏数据读还没commit的19这个19才是脏数据)拎出来作为查询结果返回给用户。也就是快照读写操作只锁当前最新版本读操作去读历史版本。读写互不阻塞极大提升了并发性能真实列数据怎么存聚簇(主键索引)索引里的一行记录InnoDB 表数据本身就是按主键组织的 B 树。PRIMARY KEY(id)那么聚簇索引里叶子节点保存完整行id 1 - 完整用户记录id 2 - 完整用户记录id 3 - 完整用户记录所以查询SELECT * FROM users WHERE id 1;可以直接通过主键索引找到完整行。二级索引里的一行记录如果有二级索引CREATE INDEX idx_age ON users(age);二级索引记录不是保存完整行而是保存age 18, id 1age 20, id 2age 25, id 3所以执行SELECT * FROM users WHERE age 18;流程通常是1. 先查二级索引 idx_age找到 id 12. 再拿 id 1 去聚簇索引查完整行3. 这一步就是回表总结MySQL InnoDB 的一行记录本质上是存储在B 树数据页中的一条 record。不只存你表里的字段而是大概包含变长字段长度信息NULL 标记记录头信息事务隐藏字段用户真实字段可能的页外字段指针