黑马八股MySQl学习
2026/7/15 19:51:59 网站建设 项目流程

链接

1.介绍

优化方面

2.如何定位慢查询

方案1:开源工具

追踪功能

方案2:Mysql自带慢日志

将慢sql记录到日志文件中

总结

生产一般不开启 损耗mysql性能

3.SQL执行慢-分析方法

获取查询语句信息

type字段

总结

4.索引概念+底层数据结构

B+树是B树的优化

2:是因为数据都在叶子节点,查找路径差不多所以稳定

3:双向指针连接,所以范围查询方便

5.聚簇索引,非聚簇索引,回表查询

回表查询(二次)

总结

单独给字段创建的索引,一般多个

6.覆盖索引,超大分页优化

基于二级索引

二级索引的叶子,一定会包含索引键 (name) + 主键 id

总结1

超大分页-覆盖索引+子查询优化

总结2

先分页查询获取表中的id然后对表的id进行排序,能筛选出分页之后的id集合,

因为id是覆盖索引,操作id效率高,最后通过id集合去到原来的表中做关联查询

效率就可以得到提升

7.索引创建的原则

总结

一般是数据量大的基础上,然后还需要查询

三个复杂查询

联合索引

索引数量

8.索引失效情况

不满足最左前缀

不正常情况,只是命中name

范围索引右边的列使用索引

在‘索引列’进行运算

字符串不加单引号

总结

最左前缀,模糊,范围,字符串计算和单引号

9.SQL优化经验

表设计的优化

SQL语句的优化

主从复制、读写分离

总结

事务相关

1.事务特性

2.并发事务带来的问题、隔离级别

并发事务带来的问题

这个幻读的情况 是在解决了不可重复读问题的情况下 就是可以重复读取了 且重复读取时一致的数据

假设表里只有 id=1、2 两个账户,事务 A 要统计余额 > 0 的账户总数:

  1. 事务 A 第一次执行:SELECT * FROM account WHERE money > 0,查到 2 条数据;
  2. 此时事务 B 新开事务,执行INSERT INTO account(id,money) VALUES(3,1000),并commit 提交
  3. 事务 A 没有结束,再次执行一模一样的SELECT * FROM account WHERE money > 0,这次查到 3 条;

事务 A 在同一个事务里,同样的查询语句,前后结果行数变多了,仿佛凭空多出一行,这就是幻读

反向场景:事务 B 删除了一条符合条件的数据并提交,A 第二次查询少了一行,也属于幻读。

即:

  • UPDATE 改已有行 → 不可重复读
  • INSERT/DELETE 增减行 → 幻读
问题操作对象触发 SQL 类型现象
不可重复读已存在的单行数据其他事务 UPDATE 更新同一行的值前后不一样
幻读整个范围的数据集其他事务 INSERT/DELETE 增删行查询到的总行数变多 / 变少,出现 / 消失新记录

解决以上问题:事务隔离

隔离级别

串行化是强制加锁串行执行解决所有并发问题,一般不用,性能差

总结

并发事务带来的问题

问题一句话根因解决思路举例
脏读读到了别人还没提交的中间数据读没有隔离,能读到未提交数据只允许读已提交事务A改 salary=9000(未提交),事务B读到 9000,A回滚了,B读到的是脏数据
不可重复读同一条数据,两次读结果不一致(被别人 UPDATE)读未加锁,两次读之间被别人改了并提交事务内读用快照(MVCC)事务A第一次读 salary=5000,事务B改成 8000 并提交,事务A再读变成 8000
幻读同一个范围查询,两次读行数不一致(被别人 INSERT/DELETE)没加间隙锁,范围查询被别人插了或删了并提交MVCC + Next-Key Lock事务A查 age>30 有 5 条,事务B插入一条并提交,事务A再查变成 6 条
丢失更新两个事务同时修改同一行,一个的更新被覆盖写没加锁,后提交的覆盖了先提交的SELECT ... FOR UPDATE 加行锁事务A读 salary=5000,事务B也读 5000,A改成 6000 提交,B改成 7000 提交,A 的更新丢失

隔离级别与问题的对应关系

隔离级别脏读不可重复读幻读怎么做到的
READ UNCOMMITTED(读未提交)✅ 存在✅ 存在✅ 存在什么都不做,直接读最新值
READ COMMITTED(读已提交)❌ 解决✅ 存在✅ 存在每次 SELECT 生成新快照
REPEATABLE READ(可重复读)⭐❌ 解决❌ 解决⚠️ InnoDB 通过 MVCC + 间隙锁基本解决事务第一次 SELECT 生成快照,后续复用;当前读加 Next-Key Lock
SERIALIZABLE(串行化)❌ 解决❌ 解决❌ 解决所有读加共享锁,写加排他锁,事务完全串行

MySQL 默认隔离级别:REPEATABLE READ

RC 和 RR 的核心区别(最关键)

RC(READ COMMITTED)—— 每次读都看最新已提交

SELECT ① → 生成快照 A 别人提交了新数据 SELECT ② → 生成快照 B(能看到新数据)← 所以会有不可重复读

RR(REPEATABLE READ)—— 进事务时拍照,之后只看这张照片

SELECT ① → 生成快照 A 别人提交了新数据 SELECT ② → 还是快照 A(看不到新数据)← 所以解决了不可重复读

⚠️ 标准 SQL 中 RR 级别不解决幻读,但 InnoDB 用MVCC(快照读)+ Next-Key Lock(当前读)两种机制配合,基本消除了幻读。


一句话速记口诀

脏读是读了没提交的,不可重复读是读了被改的,幻读是读了被插的,丢失更新是写了被覆盖的。RC每次读最新快照解决脏读,RR锁住第一次快照解决不可重复读,InnoDB再加间隙锁解决幻读,串行化全部排队

3.undo log和redo log区别

都是mysql的日志文件

redo log

内存操作完的数据没同步到磁盘,属于脏页,此时如果服务器宕机,会导致数据同步失败,内存的数据可能会消失,违背事务持久化的特性

mysql引入了undo log日志文件

机制:先写日志,延迟刷磁盘数据文件

undo log

总结

undo logredo log
一句话回滚日志,记录修改前的旧数据重做日志,记录修改后的新值
解决问题事务回滚 + MVCC(多版本并发控制)崩溃恢复(crash recovery)
记录内容修改的数据(旧值)修改的数据(新值)
作用阶段事务执行过程中事务执行过程中
保证特性原子性(Atomicity)持久性(Durability)
生命周期事务提交后可被清理(purge 线程)数据刷盘后可被覆盖(循环写入)
存储位置共享表空间(ibdata)或独立 undo 表空间redo log 文件(ib_logfile0 / ib_logfile1)
文件大小动态增长固定大小,循环写入
关联机制MVCC(通过版本链提供一致性快照)WAL(先写日志再写磁盘)
回滚应用ROLLBACK 时根据 undo log 恢复旧值
崩溃恢复重启后重放 redo log 恢复未刷盘的数据

核心记忆口诀:

undo 存旧值,用于回滚 + MVCC;redo 存新值,用于崩溃恢复。一个是往回看(回到过去),一个是往前追(恢复未来)。

细节:

redo log 细节
核心思想:WAL

Write-Ahead Logging,先写日志,再写磁盘。

为什么?因为随机 IO 慢。直接把修改写到数据页是随机写,而写 redo log 是顺序写,快得多。

组成
  • redo log buffer(内存)
  • redo log file(磁盘,ib_logfile0、ib_logfile1...)
写入时机

事务执行过程中就开始写 buffer,不是提交时才写

刷盘策略(innodb_flush_log_at_trx_commit)

这个参数控制 buffer 什么时候真正写到磁盘:

行为安全性
0每秒由后台线程刷盘丢1秒数据
1每次提交时刷盘不丢数据(最安全)
2提交时写到 OS 缓存,每秒刷盘OS 挂才丢

默认是 1,面试直接说就行。

特点
  • 物理日志:记录的是"某页某偏移处改成什么值"
  • 循环写:写满后回到开头覆盖旧的
  • checkpoint机制控制"哪些部分已经可以被覆盖"——就是对应数据页已经刷到磁盘了

undo log 细节
作用有两个,不只是回滚
  1. 1.事务回滚
  2. 2.MVCC(多版本并发控制)
两种类型
类型对应操作何时清理
insert undo logINSERT 操作事务提交后立刻可以删
update undo logUPDATE / DELETE 操作不能立刻删,要等没有事务再读旧版本

为什么 insert 的可以立刻删?因为插入的数据其他事务根本没读到过,不需要旧版本。

update/delete 的为什么不能删?因为其他事务可能正在通过 MVCC 读那个旧版本。

旧版本链

每行数据被修改时,旧版本通过 undo log 串成一条链:

当前数据 → undo log(版本3) → undo log(版本2) → undo log(版本1)

事务读数据时,沿着这条链找到自己"看得见"的那个版本。

判断依据:ReadView

ReadView 里有四个关键东西:

  • m_ids:生成 ReadView 时,还在活跃的事务 ID 列表
  • min_trx_id:活跃事务中最小的 ID
  • max_trx_id:下一个要分配的事务 ID(当前最大 + 1)
  • creator_trx_id:创建这个 ReadView 的事务 ID

判断规则:

  1. 1.数据的 trx_id等于creator_trx_id → 自己改的,可见
  2. 2.数据的 trx_id小于min_trx_id → 在 ReadView 之前就提交了,可见
  3. 3.数据的 trx_id大于等于max_trx_id → 在 ReadView 之后才开始的事务,不可见
  4. 4.数据的 trx_id在 min 和 max 之间→ 看是否在 m_ids 里:
    • 在 → 没提交,不可见
    • 不在 → 已提交,可见

不可见就沿 undo log 链继续找上一个版本。

Purge 线程

后台有个 purge 线程专门负责清理已经没有任何事务需要的 undo log。


对比总结
redo logundo log
类型物理日志逻辑日志
内容数据页修改后数据修改前的逆操作
保证持久性(D)原子性(A)+ MVCC
写入时机事务执行中事务执行中
什么时候不删checkpoint 推进后可覆盖无事务读旧版本后 purge 清理
存储位置磁盘上的 ib_logfile表空间里的回滚段(rollback segment)

面试能说清 WAL、刷盘策略、undo log 的两种类型、MVCC 的 ReadView 四个判断规则,这块就稳了。

4.mvcc多版本并发控制

实现快照读,读不阻塞写、写不阻塞读,高并发性能强,同时解决脏读、不可重复读;配合间隙锁在 RR 隔离级别消除幻读

ReadView,它只管快照读。当前读根本不走 ReadView,走的是另一条路——加锁

概述:

一个版本的 trx_id 比快照里未提交事务的最小ID还小,或者不在未提交事务列表里,就可见;否则就沿 undo log 往回找。

总结:

MVCC 速记总结

一句话

读旧版本不加锁,写最新版本才加锁。


依赖三件套

隐藏列 undo log版本链 ReadView (trx_id + (旧版本串成链, (判断哪些版本 roll_ptr) 回滚用) 对我可见)

版本链长什么样

当前行 [TRX_ID=4] ──→ undo [TRX_ID=3] ──→ undo [TRX_ID=2] ──→ undo [TRX_ID=1] 最新版本 旧版本 更旧 最早

每次修改:旧值存 undo → 当前行指向新值


ReadView 四个字段

字段一句话
m_ids此刻还没提交的事务有哪些
min_trx_id这些活跃事务里最小的 ID
max_trx_id下一个要分配的新事务 ID
creator_trx_id我自己的 ID

可见性判断(按顺序)

trx_id == 我自己? → 可见 ✅(自己改的) trx_id < min? → 可见 ✅(早就提交了) trx_id >= max? → 不可见 ❌(是未来事务) trx_id 在 min 和 max 之间? ├─ 在 m_ids 里? → 不可见 ❌(没提交) └─ 不在 m_ids 里? → 可见 ✅(已经提交了)

不可见就沿 roll_ptr 往回找,找到可见版本为止。


RC vs RR 的唯一区别

RC(读已提交):每次 SELECT → 新建 ReadView RR(可重复读):第一次 SELECT → 建 ReadView,后面复用

这就是 RR 能"可重复读"的原因——不是加锁,是复用快照。


快照读 vs 当前读

快照读(走 MVCC)当前读(不走 MVCC)
普通 SELECTSELECT ... FOR UPDATE(排它锁)
SELECT ... LOCK IN SHARE MODE(共享锁)
INSERT / UPDATE / DELETE(排它锁)

背下来的关键口诀

存旧值靠 undo log,判断能不能看靠 ReadView,RC 每次建 RR 建一次,不可见就沿链往回追。

主从同步原理

总结

分库分表

目前主库和从库存的数据一样,那此时是解决访问的压力,但读写分开来进行访问时解决不了海量数据存储的问题

分库分表策略

垂直拆分

就是按模块分

水平拆分

分摊单机压力,缩小单次检索的数据范围,路由规则很多

拆分类型拆分依据部署位置表结构特点核心解决问题
垂直分库业务功能模块多个独立数据库实例各库存放不同种类数据业务解耦,不同业务分摊服务器压力
水平分库分片路由取模/ 范围 / 哈希等)多个独立数据库实例各个库内部的表结构完全一致突破单机数据库并发、硬件瓶颈
垂直分表字段冷热、字段大小同一个数据库当中拆分出多张字段不同的子表单表字段过多大字段拖慢查询的问题
水平分表分片路由(取模/ 范围 / 哈希等)同一个数据库当中拆分出多张结构一致的子表单张表行数太多、索引臃肿的问题

总结

垂直分表垂直分库水平分表水平分库
一句话把一张大表的列拆成多张小表不同业务的表拆到不同数据库同一张表的数据按行拆到多张表同一张表的数据按行拆到多个数据库
拆的是什么列(字段)整个业务模块行(记录)行(记录)
拆分前1 张宽表1 个库放所有业务表1 张大表1 张大表在 1 个库
拆分后多张窄表(同库)多个库(不同机器)多张结构相同的表(同库)多个库,每个库结构相同(不同机器)
核心驱动单表字段太多,冷热数据分离不同业务耦合在一起,互相影响单表数据量太大(千万/亿级)单库存不下或扛不住读写压力
关联方式同库直接 JOIN应用层代码分别查询后组装同库直接查/UNION ALL中间件(ShardingSphere)路由 + 聚合
举例user 表拆成 user_base + user_profile用户库 / 订单库 / 商品库拆开orders 表拆成 orders_0 ~ orders_3orders_0 在机器A,orders_1 在机器B
分片键不需要不需要需要(如 user_id % 4)需要(如 user_id % 2)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询