MySQL

1 面试题概览

|800

2 优化

2.1 定位慢查询

2.1.1 示例问题

  1. MySQL 中如何定位慢查询?
    我们当时做压测的时候有的接口非常的慢,接口的响应时间超过了 2 秒以上,使用 MySQL 中提供的慢日志查询的功能,可以在 MySQL 的系统配置文件中开启这个慢日志的功能,并且也可以设置 SQL 执行超过多少时间来记录到一个日志文件中,我记得上一个项目配置的是 2 秒,只要 SQL 执行的时间超过了 2 秒就会记录到日志文件中,我们就可以在日志文件找到执行比较慢的 SQL 了。

2.2 SQL 执行计划

可以采用 EXPLAIN 或者 DESC 命令获取 MySQL 如何执行 SELECT 语句的信息

例如:
|950

|825

|825

2.2.1 示例问题

  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。

image-20260103182814324

B+ 树是在 B 树基础上的一种优化,使其更适合实现外存储索引结构,InnoDB 存储引擎就是使用 B+ 树实现其索引。

image-20260103182618309

B 树与 B+ 树的对比:

  1. 数据存储位置不同
  • B 树:所有节点(包括内部节点和叶子节点)都存储键和对应的数据(记录或指针)。每个键都关联实际数据。
  • B+ 树:只有叶子节点存储实际数据(或指向数据的指针),内部节点仅存储键(用于索引导航)。叶子节点通过 双向链表 顺序连接。

​ B+ 树的内部节点不存数据,因此 一个页(如 16KB)能容纳更多键,使树更“矮胖”,减少树的高度,从而 降低磁盘 I/O 次数

  1. 范围查询效率更高
  • B 树:范围查询(如 WHERE id BETWEEN 10 AND 100)需要 中序遍历整棵树,可能多次跳转到不同磁盘页。
  • B+ 树:所有数据集中在叶子节点,并且 叶子节点形成有序链表,只需定位起始点,然后 顺序扫描链表 即可。

​ B+ 树的范围查询 只需一次树遍历 + 顺序 I/O,而 B 树可能涉及大量随机 I/O,效率显著更低。

  1. 更稳定的查询性能

  • B 树:数据可能出现在任意层级,等值查询的路径长度不固定(虽然仍在 O(log n),但实际 I/O 次数可能波动)。
  • B+ 树:所有数据都在叶子层,任何查询(等值或范围)都必须走到叶子节点,路径长度一致。
    B+ 树提供 可预测且一致的查询延迟,对数据库性能调优更友好。

2.3.1 示例问题

  1. 什么是索引?

    索引是帮助 MySQL 高效获取数据的数据结构,主要是用来提高数据检索的效率,降低数据库的成本,同时通过索引列对数据进行排序,降低数据排序的成本,也能降低了 CPU 的消耗。

  2. 索引的底层数据结构了解过吗?

    MySQL 的默认的存储引擎 InnoDB 采用的 B+ 树的数据结构来存储索引,选择 B+ 树的主要的原因是:第一阶数更多,路径更短,第二个磁盘读写代价 B+ 树更低,非叶子节点只存储指针,叶子阶段存储数据,第三是 B+ 树便于扫库和区间查询,叶子节点是一个双向链表。

2.4 聚簇索引

2.4.1 聚簇索引和非聚簇索引

分类 含义 特点
聚簇索引 将数据存储和索引一起存储,索引结构的叶子节点保存了行数据 必须有,而且只有一个
非聚簇索引(二级索引) 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 可以存在多个

聚簇索引选取规则:

  1. 如果存在主键,主键索引就是聚集索引。
  2. 如果不存在主键,将使用第一个唯一(UNIQUE)索引 I 作为聚集索引。
  3. 如果表没有主键,或没有合适的唯一索引 l,则 InnoDB 会自动生成一个 rowid 作为隐藏的聚集索引。

2.4.2 回表查询

什么时候会触发回表查询?

  1. 使用了非聚簇索引(二级索引)进行查询;
  2. 查询的字段不完全包含在该非聚簇索引中(即不是“覆盖索引”)

例如在上图中使用如下查询语句:

select * from user where name='Arm';
  1. 通过索引找到 name='Arm' 对应的 主键 id = 10
  2. 再用 id = 10聚簇索引 中查找整行数据;
  3. 从整行中取出所有字段返回。✅ 发生了回表(两次 B+ 树查找)。
  4. 注意如果只需要返回 name 字段则不需要再回表查询,因为在二级索引能够直接返回字段。

2.5 覆盖索引

一个索引包含了查询所需的所有字段,使得数据库引擎无需回表(即无需访问聚簇索引或主表数据)就能返回结果。

在使用 非聚簇索引(二级索引) 时,如果查询字段不全在索引中,数据库必须:

  1. 先通过二级索引找到主键;
  2. 再用主键去 聚簇索引 中查找完整行数据(即“回表”)。

回表会带来额外 I/O 和性能开销,尤其在结果集较大时影响显著。而 覆盖索引可以完全避免回表,大幅提升查询效率。

依旧以聚簇索引的表为例,因为前两条 sql 语句不需要再回表查询,所以就是覆盖索引;而第三条语句查询 name 字段后还需要在聚簇索引中查询 gender 字段,即进行了回表查询,所以为非覆盖索引。

image-20260104205652100

覆盖索引 = 查询所需字段全部在索引中 = 无需回表 = 高性能查询

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 仍需:

  1. 找到所有 status = 1 的记录;
  2. id 排序;
  3. 跳过前 1,000,000 行,再取 10 行;
  4. 如果 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;
  1. 子查询 SELECT id ...
    • 使用 idx_status_id 覆盖索引;
    • 无需回表,快速跳过 1,000,000 行,取出 10 个 id
  2. 外层通过 id 主键 JOIN:
    • 仅回表 10 次(获取 name, email);
    • 主键查询极快(聚簇索引)。

回表次数从 1,000,010 次 → 10 次!

2.5.2 示例问题

  1. 什么是覆盖索引?

    覆盖索引是指 select 查询语句使用了索引,在返回的列必须在索引中全部能够找到,如果我们使用 id 查询,它会直接走聚集索引查询,一次索引扫描,直接返回数据,性能高。
    如果按照二级索引查询数据的时候,返回的列中没有创建索引,有可能会触发回表查询,尽量避免使用 select *,尽量在返回的列中都包含添加索引的字段

  2. MySQL 超大分页怎么处理

    大分页一般都是在数据量比较大时,我们使用了 limit 分页查询,并且需要对数据进行排序,这个时候效率就很低,我们可以采用覆盖索引和子查询来解决:先分页查询数据的 id 字段,确定了 id 之后,再用子查询来过滤,只查询这个 id 列表中的数据就可以了。因为查询 id 的时候,走的覆盖索引,所以效率可以提升很多。

2.6 索引创建原则

  1. 数据量较大,且查询比较频繁的表
  2. 常作为查询条件、排序、分组的字段
  3. 字段内容区分度高
  4. 内容较长,使用前缀索引
  5. 尽量联合索引
  6. 要控制索引的数量
  7. 如果索引 I 列不能存储 NULL 值,请在创建表时使用 NOTNULL 约束它

2.6.1 示例问题

  1. 索引创建原则有哪些?

    情况有很多,不过都有一个大前提,就是表中的数据要超过 10 万以上,我们才会创建索引,并且添加索引的字段是查询比较频繁的字段,一般也是像作为查询条件、排序字段或分组的字段这些。
    还有就是,我们通常创建索引的时候都是使用复合索引来创建,一条 sql 的返回值,尽量使用覆盖索引,如果字段的区分度不高的话,我们也会把它放在组合索引后面的字段。
    如果某一个字段的内容较长,我们会考虑使用前缀索引来使用,当然并不是所有的字段都要添加索引,这个索引的数量也要控制,因为添加索引也会导致新增改的速度变慢。

2.7 索引失效

索引失效的情况有很多,说一些自己遇到过的,不要直接背好的答案(适当思考一下)。

  1. 在索引列上带有运算;(数据库的索引是基于 列的原始值 构建的。当你在 WHERE 子句中对索引列施加运算(如 +-、函数调用等),数据库无法直接利用索引结构快速定位数据,2 同)
  2. 在索引列上使用函数;
  3. 使用 %XXX 左模糊查询,因为 mysql 是最左原则,使用 XXX% 右模糊查询是可以使用索引的,但是左模糊违背了最左原则所以不行;(根据下图所示,左边的值 a 是有序的,而 b 是无序的,所以左模糊不能使用索引,而右模糊可以)
  4. 使用范围运算,not in,in, >,< 都不行,使用 in 以及 not in 走不走索引,实际和 Mysql 的版本以及表中的数据量有关系,在 8.0 之后的版本 IN 通常是走索引的,当 IN 后面的数据在数据表中超过 30% 的匹配时是全表扫描,不走索引,因此 IN 走不走索引和后面的数据量有关系;(同 3 ,例如 where a > 1 and b = 1, 而 a > 1 对应的 b 是无序的)
  5. 查询的字段不是索引的最左字段,同样是因为最左原则;(需要从索引最左边的列开始)
  6. 字段类型不匹配,常见的隐式数据类型转换,mobile = 1356 不会走索引,转换为字符串可以查询即 mobile ='1356' 会走索引;注意:当字段类型为数字时,无论参数类型为字符串还是数字都会走索引。(1356 与 ‘mobile’ 字段数据类型不匹配,‘1356’与 ‘mobile’ 字段类型匹配)
  7. or 条件左边的是索引字段,右边的不是。也不会走索引,因为 or 是一个并集,只有两侧都有索引才能生效;
  8. 数据分布,如果 MySQL 评估使用索引比全表更慢,则不使用索引。因为索引是用来 索引少量数据 的,如果通过索引查询返回大批量的数据,则还不如走全表扫描来的快,此时索引就会失效。(>=、<=、is null 、 is not null、in、not in)。

因为在 MySQL 中,数字和字符串的比较是将字符串转为数字再进行比较的。如果查询时用字符串那么被转换的是 SQL 中的字符串,这样不会影响表索引。而如果查询时用的是数字,字段格式是字符串,意味着表中的数据格式需要转换,用字符串格式建立的索引是不匹配的,因此不使用索引。

image-20260105185055008

原理参考视频参考博客

2.8 SQL 优化经验

  • 表的设计优化

    1. 设置合适的数值(tinyint、int、bigint),根据实际情况选择;
    2. 设置合适的字符串类型(char、varchar),char 定长效率高,varchar 可变长度但效率稍低。
  • SQL 语句优化

    1. SELECT 语句务必指明字段名称(避免使用 SELECT *);
    2. SQL 语句避免造成索引失效的写法;
    3. 尽量用 union all 代替 union,union 会多一次过滤,效率低;
    4. 避免在 where 子句中对字段进行表达式操作;
    5. 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 ⚠️ 部分命中 只用到 ac 无法用索引(跳过 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

设计原则(按优先级排序)

  1. 等值查询列放左边

    • 等值条件(=IN)能“锁定”索引范围,后续列才能用于排序或范围。
    • 例如:WHERE status = 1 AND type = 'A' ORDER BY create_time → 索引应为 (status, type, create_time)
  2. 高区分度(高选择性)列优先

    • 区分度高的列(如 user_id)能更快缩小结果集。
    • 例如:status 只有 2 个值,user_id 有 100 万不同值 → user_id 放前面更高效。
  3. 范围查询或排序列放右边

    • 一旦出现范围(>, <, 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)。当事务提交之后会把所有修改信息都存放到该日志文件中,用于在刷新脏页磁盘,发生错误时,将进行数据恢复使用。

image-20260106204217576

3.2.4 undo log

回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚和 MVCC(多版本并发控制)。undo log 和 redo log 记录物理日志不一样,它是 逻辑日志

  • 可以认为当 delete 一条记录时,undo log 中会记录一条对应的 insert 记录,反之亦然,
  • 当 update 一条记录时,它记录一条对应相反的 update 记录。当执行 rollback 时,就可以从 undo log 中的逻辑记录读取到相应的内容并进行回滚。

undo log 可以 实现事务的一致性和原子性

3.2.5 示例问题

  1. 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 版本链

image-20260111153459303

不同事务或相同事务对同一条记录进行修改,会导致该记录的 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。

    image-20260111161845386

    在 undo log 版本链中,依次拿到{4,3,2,1}四条 trx_id,再分别到右侧的对比条件中执行对比。对于{4,3},右侧的四个条件均不吗,满足,但对于{2},显然可以满足 trx_id < min_trx_id,所以该查询语句拿到的就是版本链的这条数据 0x00002。

  • RR 隔离级别:仅在事务中第一次执行快照读时生成 ReadView,后续 复用 该 ReadView。

    image-20260111163304528

Read Uncommitted(RU)和 Serializable(串行化)是两个特殊的隔离级别,它们不需要使用 Read View 的主要原因是:

  • Read Uncommitted(RU)隔离级别: 在 RU 隔离级别下,事务可以读取其他事务尚未提交的数据,即脏读。这意味着不需要通过 Read View 来限制访问范围,事务可以自由地读取其他事务的未提交数据。由于没有对可见性进行严格控制,因此不需要创建或使用 Read View。
  • Serializable(串行化)隔离级别: 在 Serializable 隔离级别下,事务具有最高的隔离性,确保每次读取都能看到一致的快照。为了实现这种隔离级别,MySQL 使用锁机制来保证事务之间的串行执行。由于事务按顺序执行,并且不允许并发操作,所以不需要使用 Read View 进行可见性判断。

3.3.3 示例问题

  1. 事务的隔离性是如何保证的?(解释一下 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(数据操作语言) 通过二进制日志传到从库服务器中,然后在从库上对这些日志重新执行(也叫重做),从而使得从库和主库的数据保持同步。

image-20260106205753468
  1. Master 主库在事务提交时,会把数据变更记录在二进制日志文件 Binlog 中。

  2. 从库读取主库的二进制日志文件 Binlog ,写入到从库的中继日志 Relay Log 。

  3. slave 重做中继日志中的事件,将改变反映它自己的数据。

3.5 分库分表

3.5.1 垂直拆分

垂直分库

以表为依据,根据业务将不同表拆分到不同库。(和微服务类似)

特点:

  • 按业务对数据分级管理、维护、监控、扩展
  • 在高并发下,提高磁盘 IO 和数据量连接数
image-20260106210657233

垂直分表

拆分规则:

  • 把不常用的字段单独放在一张表
  • 把 text,blob 等大字段拆分出来放在附表中

垂直分表:以字段为依据,根据字段属性将不同字段拆分到不同表中。

特点:

  1. 冷热数据分离
  2. 减少 IO 过渡争抢,两表互不影响
image-20260106211016306

3.5.2 水平拆分

水平分库

路由规则

  • 根据 id 节点取模
  • 按 id 也就是范围路由,节点 1(1-100 万), 节点 2(100 万-200 万)

水平分库:将一个库的数据拆分到多个库中。

特点:

  • 解决了单库大数量,高并发的性能瓶颈问题
  • 提高了系统的稳定性和可用性
image-20260106211334871

水平分表

水平分表:将一个表的数据拆分到多个表中(可以在同一个库内)。

特点:

  1. 优化单一表数据量过大而产生的性能问题;
  2. 避免 I0 争抢并减少锁表的几率;
image-20260106211708632

3.5.3 具体拆分策略

  1. 水平分库,将一个库的数据拆分到多个库中,解决海量数据存储和高并发的问题
  2. 水平分表,解决单表存储和性能的问题(水平拆分可使用 mycat 等工具辅助)
  3. 垂直分库,根据业务进行拆分,高并发下提高磁盘 IO 和网络连接数
  4. 垂直分表,冷热数据分离,多表互不影响
posted @ 2026-03-19 16:57  solarlemon  阅读(0)  评论(0)    收藏  举报