MySQL
1 面试题概览

2 优化
2.1 定位慢查询
2.1.1 示例问题
- MySQL 中如何定位慢查询?
我们当时做压测的时候有的接口非常的慢,接口的响应时间超过了 2 秒以上,使用 MySQL 中提供的慢日志查询的功能,可以在 MySQL 的系统配置文件中开启这个慢日志的功能,并且也可以设置 SQL 执行超过多少时间来记录到一个日志文件中,我记得上一个项目配置的是 2 秒,只要 SQL 执行的时间超过了 2 秒就会记录到日志文件中,我们就可以在日志文件找到执行比较慢的 SQL 了。
2.2 SQL 执行计划
可以采用 EXPLAIN 或者 DESC 命令获取 MySQL 如何执行 SELECT 语句的信息

例如:



2.2.1 示例问题
- 这条 SQL 语句执行很慢,如何分析呢?
如果一条 sql 执行很慢的话,我们通常会使用 mysql 自动的执行计划 explain 来去查看这条 sql 的执行情况,比如在这里面可以通过 key 和 key_len 检查是否命中了索引,如果本身已经添加了索引,也可以判断索引是否有失效的情况;第二,可以通过 type 字段查看 sql 是否有进一步的优化空间,是否存在全索引扫描或全盘扫描,第三个可以通过 extra 建议来判断,是否出现了回表的情况,如果出现了,可以尝试添加索引或修改返回字段来修复。
2.3 索引介绍
索引(index)是帮助 MySQL 高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构(B+ 树),这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。
B 树是一种多叉路衡查找树,相对于二叉树,B 树每个节点可以有多个分支,即多叉。以一颗最大度数(max-degree)为 5(5 阶)的 b-tree 为例,那这个 B 树每个节点最多存储 4 个 key。
B+ 树是在 B 树基础上的一种优化,使其更适合实现外存储索引结构,InnoDB 存储引擎就是使用 B+ 树实现其索引。
B 树与 B+ 树的对比:
- 数据存储位置不同
- B 树:所有节点(包括内部节点和叶子节点)都存储键和对应的数据(记录或指针)。每个键都关联实际数据。
- B+ 树:只有叶子节点存储实际数据(或指向数据的指针),内部节点仅存储键(用于索引导航)。叶子节点通过 双向链表 顺序连接。
B+ 树的内部节点不存数据,因此 一个页(如 16KB)能容纳更多键,使树更“矮胖”,减少树的高度,从而 降低磁盘 I/O 次数。
- 范围查询效率更高
- B 树:范围查询(如
WHERE id BETWEEN 10 AND 100)需要 中序遍历整棵树,可能多次跳转到不同磁盘页。 - B+ 树:所有数据集中在叶子节点,并且 叶子节点形成有序链表,只需定位起始点,然后 顺序扫描链表 即可。
B+ 树的范围查询 只需一次树遍历 + 顺序 I/O,而 B 树可能涉及大量随机 I/O,效率显著更低。
-
更稳定的查询性能
- B 树:数据可能出现在任意层级,等值查询的路径长度不固定(虽然仍在 O(log n),但实际 I/O 次数可能波动)。
- B+ 树:所有数据都在叶子层,任何查询(等值或范围)都必须走到叶子节点,路径长度一致。
B+ 树提供 可预测且一致的查询延迟,对数据库性能调优更友好。
2.3.1 示例问题
-
什么是索引?
索引是帮助 MySQL 高效获取数据的数据结构,主要是用来提高数据检索的效率,降低数据库的成本,同时通过索引列对数据进行排序,降低数据排序的成本,也能降低了 CPU 的消耗。
-
索引的底层数据结构了解过吗?
MySQL 的默认的存储引擎 InnoDB 采用的 B+ 树的数据结构来存储索引,选择 B+ 树的主要的原因是:第一阶数更多,路径更短,第二个磁盘读写代价 B+ 树更低,非叶子节点只存储指针,叶子阶段存储数据,第三是 B+ 树便于扫库和区间查询,叶子节点是一个双向链表。
2.4 聚簇索引
2.4.1 聚簇索引和非聚簇索引
| 分类 | 含义 | 特点 |
|---|---|---|
| 聚簇索引 | 将数据存储和索引一起存储,索引结构的叶子节点保存了行数据 | 必须有,而且只有一个 |
| 非聚簇索引(二级索引) | 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 | 可以存在多个 |
聚簇索引选取规则:
- 如果存在主键,主键索引就是聚集索引。
- 如果不存在主键,将使用第一个唯一(UNIQUE)索引 I 作为聚集索引。
- 如果表没有主键,或没有合适的唯一索引 l,则 InnoDB 会自动生成一个 rowid 作为隐藏的聚集索引。
2.4.2 回表查询
什么时候会触发回表查询?
- 使用了非聚簇索引(二级索引)进行查询;
- 查询的字段不完全包含在该非聚簇索引中(即不是“覆盖索引”)
例如在上图中使用如下查询语句:
select * from user where name='Arm';
- 通过索引找到
name='Arm'对应的 主键 id = 10; - 再用
id = 10去 聚簇索引 中查找整行数据; - 从整行中取出所有字段返回。✅ 发生了回表(两次 B+ 树查找)。
- 注意如果只需要返回
name字段则不需要再回表查询,因为在二级索引能够直接返回字段。
2.5 覆盖索引
一个索引包含了查询所需的所有字段,使得数据库引擎无需回表(即无需访问聚簇索引或主表数据)就能返回结果。
在使用 非聚簇索引(二级索引) 时,如果查询字段不全在索引中,数据库必须:
- 先通过二级索引找到主键;
- 再用主键去 聚簇索引 中查找完整行数据(即“回表”)。
回表会带来额外 I/O 和性能开销,尤其在结果集较大时影响显著。而 覆盖索引可以完全避免回表,大幅提升查询效率。
依旧以聚簇索引的表为例,因为前两条 sql 语句不需要再回表查询,所以就是覆盖索引;而第三条语句查询 name 字段后还需要在聚簇索引中查询 gender 字段,即进行了回表查询,所以为非覆盖索引。
覆盖索引 = 查询所需字段全部在索引中 = 无需回表 = 高性能查询
2.5.1 结合 MySQL 超大分页的问题
问题根源:LIMIT offset, size 的缺陷
-- 普通分页(性能差)
SELECT id, name, email FROM users
WHERE status = 1
ORDER BY id
LIMIT 1000000, 10;
即使 id 是主键,status 有索引,MySQL 仍需:
- 找到所有
status = 1的记录; - 按
id排序; - 跳过前 1,000,000 行,再取 10 行;
- 如果
SELECT字段不在索引中,还需 回表 1,000,010 次!
💥 瓶颈 = 跳过大量行 + 高频回表
优化方案:覆盖索引+延迟关联
优化后的分页查询:
-- 优化后的分页查询
SELECT u.id, u.name, u.email
FROM users u
INNER JOIN (
SELECT id
FROM users
WHERE status = 1
ORDER BY id
LIMIT 1000000, 10
) AS tmp ON u.id = tmp.id;
- 子查询
SELECT id ...:- 使用
idx_status_id覆盖索引; - 无需回表,快速跳过 1,000,000 行,取出 10 个
id;
- 使用
- 外层通过
id主键 JOIN:- 仅回表 10 次(获取
name,email); - 主键查询极快(聚簇索引)。
- 仅回表 10 次(获取
✅ 回表次数从 1,000,010 次 → 10 次!
2.5.2 示例问题
-
什么是覆盖索引?
覆盖索引是指 select 查询语句使用了索引,在返回的列必须在索引中全部能够找到,如果我们使用 id 查询,它会直接走聚集索引查询,一次索引扫描,直接返回数据,性能高。
如果按照二级索引查询数据的时候,返回的列中没有创建索引,有可能会触发回表查询,尽量避免使用select *,尽量在返回的列中都包含添加索引的字段 -
MySQL 超大分页怎么处理
大分页一般都是在数据量比较大时,我们使用了 limit 分页查询,并且需要对数据进行排序,这个时候效率就很低,我们可以采用覆盖索引和子查询来解决:先分页查询数据的 id 字段,确定了 id 之后,再用子查询来过滤,只查询这个 id 列表中的数据就可以了。因为查询 id 的时候,走的覆盖索引,所以效率可以提升很多。
2.6 索引创建原则
- 数据量较大,且查询比较频繁的表
- 常作为查询条件、排序、分组的字段
- 字段内容区分度高
- 内容较长,使用前缀索引
- 尽量联合索引
- 要控制索引的数量
- 如果索引 I 列不能存储 NULL 值,请在创建表时使用 NOTNULL 约束它
2.6.1 示例问题
-
索引创建原则有哪些?
情况有很多,不过都有一个大前提,就是表中的数据要超过 10 万以上,我们才会创建索引,并且添加索引的字段是查询比较频繁的字段,一般也是像作为查询条件、排序字段或分组的字段这些。
还有就是,我们通常创建索引的时候都是使用复合索引来创建,一条 sql 的返回值,尽量使用覆盖索引,如果字段的区分度不高的话,我们也会把它放在组合索引后面的字段。
如果某一个字段的内容较长,我们会考虑使用前缀索引来使用,当然并不是所有的字段都要添加索引,这个索引的数量也要控制,因为添加索引也会导致新增改的速度变慢。
2.7 索引失效
索引失效的情况有很多,说一些自己遇到过的,不要直接背好的答案(适当思考一下)。
- 在索引列上带有运算;(数据库的索引是基于 列的原始值 构建的。当你在 WHERE 子句中对索引列施加运算(如
+、-、函数调用等),数据库无法直接利用索引结构快速定位数据,2 同) - 在索引列上使用函数;
- 使用 %XXX 左模糊查询,因为 mysql 是最左原则,使用 XXX% 右模糊查询是可以使用索引的,但是左模糊违背了最左原则所以不行;(根据下图所示,左边的值 a 是有序的,而 b 是无序的,所以左模糊不能使用索引,而右模糊可以)
- 使用范围运算,not in,in, >,< 都不行,使用 in 以及 not in 走不走索引,实际和 Mysql 的版本以及表中的数据量有关系,在 8.0 之后的版本 IN 通常是走索引的,当 IN 后面的数据在数据表中超过 30% 的匹配时是全表扫描,不走索引,因此 IN 走不走索引和后面的数据量有关系;(同 3 ,例如 where a > 1 and b = 1, 而 a > 1 对应的 b 是无序的)
- 查询的字段不是索引的最左字段,同样是因为最左原则;(需要从索引最左边的列开始)
- 字段类型不匹配,常见的隐式数据类型转换,mobile = 1356 不会走索引,转换为字符串可以查询即 mobile ='1356' 会走索引;注意:当字段类型为数字时,无论参数类型为字符串还是数字都会走索引。(1356 与 ‘mobile’ 字段数据类型不匹配,‘1356’与 ‘mobile’ 字段类型匹配)
- or 条件左边的是索引字段,右边的不是。也不会走索引,因为 or 是一个并集,只有两侧都有索引才能生效;
- 数据分布,如果 MySQL 评估使用索引比全表更慢,则不使用索引。因为索引是用来 索引少量数据 的,如果通过索引查询返回大批量的数据,则还不如走全表扫描来的快,此时索引就会失效。(>=、<=、is null 、 is not null、in、not in)。
因为在 MySQL 中,数字和字符串的比较是将字符串转为数字再进行比较的。如果查询时用字符串那么被转换的是 SQL 中的字符串,这样不会影响表索引。而如果查询时用的是数字,字段格式是字符串,意味着表中的数据格式需要转换,用字符串格式建立的索引是不匹配的,因此不使用索引。

2.8 SQL 优化经验
-
表的设计优化
- 设置合适的数值(tinyint、int、bigint),根据实际情况选择;
- 设置合适的字符串类型(char、varchar),char 定长效率高,varchar 可变长度但效率稍低。
-
SQL 语句优化
- SELECT 语句务必指明字段名称(避免使用 SELECT *);
- SQL 语句避免造成索引失效的写法;
- 尽量用 union all 代替 union,union 会多一次过滤,效率低;
- 避免在 where 子句中对字段进行表达式操作;
- join 优化:能用 inner join 就不用 left join、right join。
-
主从复制、读写分离
如果数据库的使用场景读的操作比较多的时候,为避免写操作所造成的性能影响,可以采用读写分离的结构。
2.9 最佳左前缀
在 MySQL(尤其是 InnoDB 引擎)中,“最佳左前缀法则”(也称“最左前缀原则”,Leftmost Prefix Principle)是 联合索引(复合索引)能否被有效使用的核心规则。它决定了查询条件是否能命中索引、是否需要回表、是否触发排序等关键性能问题。
联合索引 (col1, col2, col3) 只有从最左列开始、连续地使用列,才能有效利用该索引。 换句话说:索引的使用必须“从左到右,不能跳过中间列”
假设创建索引:
CREATE INDEX idx_a_b_c ON table_name (a, b, c);
能命中索引的查询:
| 查询条件 | 是否命中索引 | 说明 |
|---|---|---|
WHERE a = 1 |
✅ | 使用了最左列 a |
WHERE a = 1 AND b = 2 |
✅ | 连续使用 a, b |
WHERE a = 1 AND b = 2 AND c = 3 |
✅ | 全部使用 |
WHERE a = 1 ORDER BY b, c |
✅ | 排序字段在索引中且顺序一致 |
WHERE a = 1 AND c = 3 |
⚠️ 部分命中 | 只用到 a,c 无法用索引(跳过 b) |
❌ 无法命中索引(或只能部分命中):
| 查询条件 | 问题 |
|---|---|
WHERE b = 2 |
跳过最左列 a,全表扫描 |
WHERE c = 3 |
同上 |
WHERE b = 2 AND c = 3 |
跳过 a,无法使用索引 |
WHERE a = 1 ORDER BY c |
ORDER BY 跳过 b,可能触发 filesort |
🔍 注意:
WHERE a = 1 AND c = 3虽然能用索引定位到a=1的范围,但c=3无法加速,仍需在a=1的结果集中逐行过滤c。
设计原则(按优先级排序)
-
等值查询列放左边
- 等值条件(
=、IN)能“锁定”索引范围,后续列才能用于排序或范围。 - 例如:
WHERE status = 1 AND type = 'A' ORDER BY create_time→ 索引应为(status, type, create_time)
- 等值条件(
-
高区分度(高选择性)列优先
- 区分度高的列(如
user_id)能更快缩小结果集。 - 例如:
status只有 2 个值,user_id有 100 万不同值 →user_id放前面更高效。
- 区分度高的列(如
-
范围查询或排序列放右边
-
一旦出现范围(
>,<,BETWEEN,LIKE 'abc%')或ORDER BY,其右侧的列将无法使用索引。 -
例如:
WHERE a = 1 AND b > 10 AND c = 5- 若索引为
(a, b, c):a(等值)→b(范围)→c无法用索引 - 所以应把
c放在b前?不行! 因为b是范围,必须放最后。 - 正确索引:
(a, c, b)❌ —— 但b是范围,c是等值,所以: → 先等值,再范围:(a, c, b)✅(a=,c=,b>)
- 若索引为
-
✨ 黄金口诀: “等值在左,范围在右;排序同序,覆盖优先。”
3 事务
3.1 事务的特性
- 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
- 一致性(Consistency):事务完成时,必须使所有数据都保持一致状态。
- 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
- 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
回答时最好结合转账的案例进行说明。
3.2 并发事务
3.2.1 并发事务问题
| 问题 | 描述 |
|---|---|
| 脏读 | 一个事务读到另一个事务还没有提交的数据。 |
| 不可重复读 | 一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读。 |
| 幻读 | 一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了“幻影”。 |
3.2.2 对事务进行隔离
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| Read uncommitted 未提交读 | 不可解决 | 不可解决 | 不可解决 |
| Read committed 读已提交 | 可解决 | 不可解决 | 不可解决 |
| Repeatable read(默认)可重复读 | 可解决 | 可解决 | 不可解决 |
| Serializable 串行化 | 可解决 | 可解决 | 可解决 |
事务隔离级别越高,数据越安全,但是性能越低。
3.2.3 redo log
redo log(重做日志)是 InnoDB 存储引擎独有的,它让 MySQL 拥有了崩溃恢复能力。比如 MySQL 实例挂了或宕机了,重启时,InnoDB 存储引擎会使用 redo log 恢复数据,保证数据的 持久性与完整性。
由两部分组成:内存中的重做日志缓冲(redo log buffer)以及磁盘中的重做日志文件(redo log file)。当事务提交之后会把所有修改信息都存放到该日志文件中,用于在刷新脏页磁盘,发生错误时,将进行数据恢复使用。
3.2.4 undo log
回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚和 MVCC(多版本并发控制)。undo log 和 redo log 记录物理日志不一样,它是 逻辑日志。
- 可以认为当 delete 一条记录时,undo log 中会记录一条对应的 insert 记录,反之亦然,
- 当 update 一条记录时,它记录一条对应相反的 update 记录。当执行 rollback 时,就可以从 undo log 中的逻辑记录读取到相应的内容并进行回滚。
undo log 可以 实现事务的一致性和原子性。
3.2.5 示例问题
- undo log 和 redo log 的区别
- redolog:记录的是数据页的物理变化,服务宕机可用来同步数据
- undolog:记录的是逻辑日志,当事务回滚时,通过逆操作恢复原来的数据
- redolog:保证了事务的持久性,undolog 保证了事务的 原子性和一致性
3.3 MVCC
Multi-Version Concurrency Control,多版本并发控制。目的主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁。这里的多版本指的是数据库中 同时存在多个版本的数据,并不是整个数据库的多个版本,而是某一条记录的多个版本同时存在。
在理解 MVCC 之前,需要先了解当前读和快照读。
3.3.1 当前读和快照读
当前读
在 MySQL 中,当前读是一种读取数据的操作方式,它可以直接 读取最新的数据版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。MySQL 提供了两种实现当前读的机制:
一致性读(Consistent Read):
- 默认隔离级别下(可重复读),MySQL 使用一致性读来实现当前读。
- 在事务开始时,MySQL 会创建一个一致性视图(Consistent View),该视图反映了事务开始时刻数据库的快照。
- 在事务执行期间,无论其他事务对数据进行了何种修改,事务始终使用一致性视图来读取数据。
- 这样可以保证在同一个事务内多次查询返回的结果是一致的,从而实现了当前读。
锁定读(Locking Read):
- 锁定读是一种特殊情况下的当前读方式,在某些场景下使用。
- 当使用锁定读时,MySQL 会在执行读取操作前获取共享锁或排他锁,以确保数据的一致性。
- 共享锁(Shared Lock)允许多个事务同时读取同一数据,而排他锁(Exclusive Lock)则阻止其他事务读取或写入该数据。
- 锁定读适用于需要严格控制并发访问的场景,但由于加锁带来的性能开销较大,建议仅在必要时使用。
当前读实际上是一种加锁的操作,是 悲观锁 的实现。
快照读
快照读是在读取数据时读取(不加锁)一个一致性视图中的数据,MySQL 使用 MVCC 机制来支持快照读。具体而言,每个事务在开始时会创建一个一致性视图(Consistent View),该视图反映了事务开始时刻数据库的快照。这个一致性视图会记录当前事务开始时已经提交的数据版本。
当执行查询操作时,MySQL 会根据事务的一致性视图来决定可见的数据版本。只有那些在事务开始之前已经提交的数据版本才是可见的,未提交的数据或在事务开始后修改的数据则对当前事务不可见。像不加锁的 select 操作就是快照读,即不加锁的非阻塞读。
快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。
注意:快照读的前提是隔离级别不是串行级别,在串行级别下,事务之间完全串行执行,快照读会退化为当前读
MVCC 主要就是为了实现读-写冲突不加锁,而这个读指的就是快照读,是 乐观锁 的实现。
3.3.2 原理解析
隐藏字段
MySQL 中的行数据,除了我们肉眼能看到的字段之外,其实还包含了一些隐藏字段,它们在内部使用,默认情况下不会显示给用户。
| 字段 | 含义 |
|---|---|
| DB_ROW_ID | 最近修改事务 ID,记录插入这条记录或最后一次修改该记录的事务 ID。如果该数据被修改了两次则 id = 2. |
| DB_TRX_ID | 回滚指针,指向这条记录的 上一个版本,用于配合 undolog,指向上一个版本。 |
| DB_ROLL_PTR | 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。 |
undo log
回滚日志,在 insert、update、delete 的时候产生的便于数据回滚的日志。
当 insert 的时候,产生的 undolog 日志只在回滚时需要,在事务提交后,可被立即删除。
而 update、delete 的时候,产生的 undolog 日志不仅在回滚时需要,mvcc 版本访问也需要,不会立即被删除。
undo log 版本链
不同事务或相同事务对同一条记录进行修改,会导致该记录的 undolog 生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是
最早的旧记录。
readview
ReadView(读视图)是快照读 SQL 执行时 MVCC 提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id,用来判断版本链中的哪个版本对当前事务是可见的。
| 字段 | 含义 |
|---|---|
| m_ids | 当前活跃的事务 ID 集合(未提交) |
| min_trx_id | 最小活跃事务 ID |
| max_trx_id | 预分配事务 ID,当前最大事务 ID+1(事务 ID 是自增的) |
| creator_trx_id | ReadView 创建者的事务 ID |
Read View 只针对 RC(Read commited) 和 RR(Repeatable read) 级别
-
RC 隔离级别:务中每一次执行快照读时生成 ReadView。下图所示,会有多个不同的 readview。

在 undo log 版本链中,依次拿到{4,3,2,1}四条 trx_id,再分别到右侧的对比条件中执行对比。对于{4,3},右侧的四个条件均不吗,满足,但对于{2},显然可以满足
trx_id < min_trx_id,所以该查询语句拿到的就是版本链的这条数据 0x00002。 -
RR 隔离级别:仅在事务中第一次执行快照读时生成 ReadView,后续 复用 该 ReadView。
Read Uncommitted(RU)和 Serializable(串行化)是两个特殊的隔离级别,它们不需要使用 Read View 的主要原因是:
- Read Uncommitted(RU)隔离级别: 在 RU 隔离级别下,事务可以读取其他事务尚未提交的数据,即脏读。这意味着不需要通过 Read View 来限制访问范围,事务可以自由地读取其他事务的未提交数据。由于没有对可见性进行严格控制,因此不需要创建或使用 Read View。
- Serializable(串行化)隔离级别: 在 Serializable 隔离级别下,事务具有最高的隔离性,确保每次读取都能看到一致的快照。为了实现这种隔离级别,MySQL 使用锁机制来保证事务之间的串行执行。由于事务按顺序执行,并且不允许并发操作,所以不需要使用 Read View 进行可见性判断。
3.3.3 示例问题
-
事务的隔离性是如何保证的?(解释一下 MVCC)
MySQL 中的多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突。
- 隐藏字段:
- rx_id(事务 id),记录每一次操作的事务 id,是自增的
- roll_pointer(回滚指针),指向上一个版本的事务版本记录地址
- undo log:
- 回滚日志,存储老版本数据
- 版本链:多个事务并行操作某一行记录,记录不同事务修改数据的版本,通过 roll_pointer 指针形成一个链表
- ReadView 解决的是一个事务查询选择版本的问题
- 根据 readView 的匹配规则和当前的一些事务 id 判断该访问那个版本的数据
- 不同的隔离级别快照读是不一样的,最终的访问的结果不一样
RC:每一次执行快照读时生成 ReadView
RR:仅在事务中第一次执行快照读时生成 ReadView,后续复用
事务的隔离性是由锁和 mvcc 实现的。
其中 mvcc 的意思是多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,它的底层实现主要是分为了三个部分,第一个是隐藏字段,第二个是 undolog 日志,第三个是 readView 读视图。隐藏字段是指:在 mysql 中给每个表都设置了隐藏字段,有一个是 trxid(事务 id),记录每一次操作的事务 id,是自增的;另一个字段是 roll_pointer(回滚指针),指向上一个版本的事务版本记录地址 undolog 主要的作用是记录回滚日志,存储老版本数据,在内部会形成一个版本链,在多个事务并行操作某一行记录,记录不同事务修改数据的版本,通过 roll_pointer 指针形成一个链表 ReadView 解决的是一个事务查询选择版本的问题,在内部定义了一些匹配规则和当前的一些事务 id 判断该访问那个版本的数据,不同的隔离级别快照读是不一样的,最终的访问的结果不一样。如果是 RC 隔离级别,每一次执行快照读时生成 ReadView,如果是 RR 隔离级别仅在事务中第一次执行快照读时生成 ReadView,后续复用。 - 隐藏字段:
3.4 主从同步/复制原理
主从同步是指将主数据库的 DDL(数据定义语言) 和 DML(数据操作语言) 通过二进制日志传到从库服务器中,然后在从库上对这些日志重新执行(也叫重做),从而使得从库和主库的数据保持同步。
-
Master 主库在事务提交时,会把数据变更记录在二进制日志文件 Binlog 中。
-
从库读取主库的二进制日志文件 Binlog ,写入到从库的中继日志 Relay Log 。
-
slave 重做中继日志中的事件,将改变反映它自己的数据。
3.5 分库分表
3.5.1 垂直拆分
垂直分库
以表为依据,根据业务将不同表拆分到不同库。(和微服务类似)
特点:
- 按业务对数据分级管理、维护、监控、扩展
- 在高并发下,提高磁盘 IO 和数据量连接数
垂直分表
拆分规则:
- 把不常用的字段单独放在一张表
- 把 text,blob 等大字段拆分出来放在附表中
垂直分表:以字段为依据,根据字段属性将不同字段拆分到不同表中。
特点:
- 冷热数据分离
- 减少 IO 过渡争抢,两表互不影响
3.5.2 水平拆分
水平分库
路由规则
- 根据 id 节点取模
- 按 id 也就是范围路由,节点 1(1-100 万), 节点 2(100 万-200 万)
水平分库:将一个库的数据拆分到多个库中。
特点:
- 解决了单库大数量,高并发的性能瓶颈问题
- 提高了系统的稳定性和可用性
水平分表
水平分表:将一个表的数据拆分到多个表中(可以在同一个库内)。
特点:
- 优化单一表数据量过大而产生的性能问题;
- 避免 I0 争抢并减少锁表的几率;
3.5.3 具体拆分策略
- 水平分库,将一个库的数据拆分到多个库中,解决海量数据存储和高并发的问题
- 水平分表,解决单表存储和性能的问题(水平拆分可使用 mycat 等工具辅助)
- 垂直分库,根据业务进行拆分,高并发下提高磁盘 IO 和网络连接数
- 垂直分表,冷热数据分离,多表互不影响

浙公网安备 33010602011771号