课程:B站大学
记录学习极客时间团队MySQL45讲,进阶数据分析和数据处理
MySQL中的事务和索引的作用
- [第一章:事务隔离 ------ 为什么你改了我还看不见?](#第一章:事务隔离 —— 为什么你改了我还看不见?)
-
- [1.1 事务基础](#1.1 事务基础)
- [1.2 四大隔离级别](#1.2 四大隔离级别)
- [1.3 实现原理:MVCC + Read View](#1.3 实现原理:MVCC + Read View)
- [1.4 长事务的危害(重点!)](#1.4 长事务的危害(重点!))
- [1.5 最佳实践](#1.5 最佳实践)
- [第二章:深入浅出索引(上)------ 数据结构与B+树](#第二章:深入浅出索引(上)—— 数据结构与B+树)
- [第三章:深入浅出索引(下)------ 覆盖索引与索引优化](#第三章:深入浅出索引(下)—— 覆盖索引与索引优化)
-
- [3.1 回表现象](#3.1 回表现象)
- [3.2 覆盖索引(Covering Index)](#3.2 覆盖索引(Covering Index))
- [3.3 最左前缀原则](#3.3 最左前缀原则)
- [3.4 索引下推 ICP(Index Condition Pushdown)](#3.4 索引下推 ICP(Index Condition Pushdown))
第一章:事务隔离 ------ 为什么你改了我还看不见?
1.1 事务基础
事务保证一组SQL要么全成功,要么全失败(如转账:查询→扣减→更新)。
⚠️ 只有 InnoDB 支持事务,MyISAM 不支持,这是 InnoDB 取代 MyISAM 的核心原因之一。
ACID 四大特性 :原子性、一致性、隔离性、持久性。本章聚焦隔离性。
并发三大问题:
| 问题 | 定义 |
|---|---|
| 脏读 | 读到其他事务未提交的修改 |
| 不可重复读 | 同一事务内两次读同一行,结果不一致 |
| 幻读 | 同一事务内两次范围查询,结果集行数不一致 |
1.2 四大隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 核心特点 |
|---|---|---|---|---|
| Read Uncommitted | ❌ | ❌ | ❌ | 读未提交的修改 |
| Read Committed | ✅ | ❌ | ❌ | 只有提交后才可见 |
| Repeatable Read(默认) | ✅ | ✅ | ❌* | 事务期间视图不变 |
| Serializable | ✅ | ✅ | ✅ | 读写加锁,完全串行 |
*InnoDB 在 RR 级别通过 MVCC + 间隙锁解决幻读。
经典示例(初始值=1,两个事务操作):
| 隔离级别 | V1 | V2 | V3 |
|---|---|---|---|
| RC | 1 | 2 | 2 |
| RR | 1 | 1 | 2 |
1.3 实现原理:MVCC + Read View
不同隔离级别的核心差异在于何时创建 Read View:
- RC:每条SQL执行时创建新视图(读提交)
- RR:事务启动时创建,全程复用(可重复读)
- RU:无视图,直接读最新值(读未提交)
- Serializable:读写加锁(串行性)
MVCC 多版本链:

每条记录更新时生成 undo log(回滚日志),形成版本链。不同事务的 Read View 对应不同版本。
回滚日志清理时机:当系统中没有比该日志更早的 Read View 时,才会被删除。
1.4 长事务的危害(重点!)
- 空间暴涨:undo log 无法清理 → 曾有案例:数据 20GB,undo log 膨胀到 200GB
- 锁资源占用:长事务持有行锁/表锁,阻塞其他事务
监控长事务:
sql
SELECT * FROM information_schema.innodb_trx
WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60;
1.5 最佳实践
sql
-- 推荐:显式启动
BEGIN;
-- ...SQL...
COMMIT;
-- 优化:提交后自动开启下一个事务(省去BEGIN开销)
COMMIT WORK AND CHAIN;
-- 查看隔离级别
SHOW VARIABLES LIKE 'transaction_isolation';
-- Oracle迁移时设为RC
SET GLOBAL transaction_isolation = 'READ-COMMITTED';
⚠️ 避免
SET autocommit=0,长连接下极易产生意外长事务。
第二章:深入浅出索引(上)------ 数据结构与B+树
2.1 三种索引模型对比
| 模型 | 等值查询 | 范围查询 | 更新 | 适用场景 |
|---|---|---|---|---|
| 哈希表 | O(1) | ❌ | ✅ | 等值查询NoSQL |
| 有序数组 | O(logN) | ✅ | ❌ | 静态数据 |
| 二叉搜索树 | O(logN) | ✅ | ✅ | MySQL InnoDB |
为什么不用二叉树?
- 100万节点 → 树高20 → 20次磁盘IO(机械盘单次IO≈10ms)
- B+树 N≈1200(16KB页/索引大小),树高4可存 17亿 数据
哈希表定义
哈希表是一种以键 - 值(key-value)存储数据的结构,我们只要输入待查找的值即 key,
就可以找到其对应的值即 Value。哈希的思路很简单,把值放在数组里,用一个哈希函数把
key 换算成一个确定的位置,然后把 value 放在数组的这个位置。
适合:哈希表这种结构适用于只有等值查询的场景

有序数组
有序数组在等值查询和范围查询场景中的性能就都非常优秀、可以先用二分法擦汗寻

,有序数组索引只适用于静态存储引擎,比如你要保存的是 2017 年某个城市的所有人
口信息,这类不会再修改的数据
2.2 二叉搜索树结构

- 非叶子节点:只存索引键值 + 指针,不存数据
- 叶子节点:存完整数据(聚簇索引)或主键值(二级索引)
- 叶子链表:双向链表连接 → 支持高效范围查询
2.3 主键索引 vs 二级索引
| 类型 | 别名 | 叶子节点存 | 查询特点 |
|---|---|---|---|
| 主键索引 | 聚簇索引 | 整行数据 | 只需扫一棵B+树 |
| 二级索引 | 非聚簇索引 | 主键值 | 需回表查主键索引 |
✅ 尽量用主键查询,避免回表开销。
2.4 页分裂与自增主键
- 自增主键:顺序追加 → 极少页分裂 → 空间利用率高
- 随机主键(如UUID):频繁页分裂 → 空间利用率降至约50%
为什么推荐自增主键?
| 维度 | 自增主键 | 业务主键(如身份证号) |
|---|---|---|
| 插入性能 | 顺序写,无挪动 | 随机写,频繁挪动 |
| 二级索引空间 | int 4~8字节 | varchar ~20字节 |
| 结论 | 节省约60%空间 | 不推荐 |
例外:表只有一个索引且是唯一索引 → 可用业务字段做主键(KV场景)。
2.5 重建索引的正确方式
sql
-- 普通索引重建(先建后删,避免抖动)
ALTER TABLE T ADD INDEX idx_k_new(k);
ALTER TABLE T DROP INDEX idx_k;
-- 主键/全表重建(Online DDL,5.6+)
ALTER TABLE T ENGINE=InnoDB;
❌ 错误:先删主键再建主键 → 全表重建两次!
第三章:深入浅出索引(下)------ 覆盖索引与索引优化
3.1 回表现象
表结构:
sql
CREATE TABLE T (
ID int PRIMARY KEY,
k int NOT NULL,
s varchar(16),
INDEX k(k)
) ENGINE=InnoDB;
查询 :SELECT * FROM T WHERE k BETWEEN 3 AND 5
执行流程:

| 步骤 | 操作 | 说明 |
|---|---|---|
| ① | k索引树找到 k=3 → ID=300 | 扫描二级索引 |
| ② | 回表 → 主键索引查整行R3 | 随机IO |
| ③ | k索引树找 k=5 → ID=500 | 扫描二级索引 |
| ④ | 回表 → 主键索引查整行R4 | 随机IO |
| ⑤ | k=6 不满足条件,结束 | --- |
代价:扫描3条索引记录,回表2次。
3.2 覆盖索引(Covering Index)
定义 :索引包含了查询所需的所有字段 → 无需回表。
sql
-- 原查询(需回表2次)
SELECT * FROM T WHERE k BETWEEN 3 AND 5;
-- 优化后(覆盖索引,0次回表)
SELECT ID FROM T WHERE k BETWEEN 3 AND 5;
实战场景:市民表高频查询「身份证号 → 姓名」
sql
-- 建立联合索引 (身份证号, 姓名)
-- 查询只需扫描联合索引,无需回表
SELECT 姓名 FROM 市民 WHERE 身份证号 = 'xxx';
⚠️ 权衡:索引有维护成本,只为高频请求建冗余联合索引。
3.3 最左前缀原则
B+树索引项按字段顺序排序,只要满足最左前缀就能利用索引。
联合索引 (name, age) 结构:

生效规则:
| 查询条件 | 是否用索引 | 说明 |
|---|---|---|
WHERE name='张三' |
✅ | 命中最左字段 |
WHERE name LIKE '张%' |
✅ | 命中最左M个字符 |
WHERE age=10 |
❌ | 不满足最左前缀 |
字段顺序设计原则:
- 复用优先 :已有
(a,b)→ 无需再建(a) - 空间优先 :需单独建
(b)时,把字段小的 放联合索引前列- 例:
name比age大 → 建(name, age)+ 单建(age)
- 例:
3.4 索引下推 ICP(Index Condition Pushdown)
MySQL 5.6+ 引入,在索引遍历时提前过滤不满足条件的记录,减少回表次数。
场景 :(name, age) 联合索引
sql
SELECT * FROM tuser
WHERE name LIKE '张%' AND age=10 AND ismale=1;
对比 :
无索引下推执行流程

索引下推执行流程

| 版本 | 机制 | 回表次数 |
|---|---|---|
| 5.5及之前 | 只用 name 定位,全部回表后在Server层过滤 |
4次 |
| 5.6+(ICP) | 引擎层直接用 age 过滤,不满足的跳过 |
2次 |
✅ ICP 将过滤条件下推到引擎层,大幅减少回表IO。
如果觉得本文对你有帮助,欢迎点赞 + 收藏 + 关注,持续更新MySQL实战系列笔记!