MySQL进阶详解

O泡李华 5

MySQL 进阶详解

本章位置:第二阶段 Java 核心框架
前置知识:MySQL 基础、JDBC、MyBatis、Web CRUD
下一篇:Maven / HTTP / Tomcat
学习目标:系统掌握索引、联合索引、最左前缀、索引选择性、执行计划、事务、隔离级别、MVCC、锁、死锁、JOIN、多表查询、存储引擎和 SQL 优化。


一、MySQL 进阶到底学什么

MySQL 基础阶段主要解决:

会不会写 SQL

MySQL 进阶主要解决:

为什么这样写更快

为什么有时候索引失效

事务为什么会出现脏读、不可重复读

为什么会死锁

JOIN 怎么执行

EXPLAIN 怎么看

数据量变大以后怎么优化

二、数据库优化的核心思路

数据库性能问题通常围绕:

数据怎么存

数据怎么找

SQL 怎么执行

事务怎么并发

锁怎么竞争

可以归纳为:

表设计
索引设计
SQL 设计
事务设计
架构设计

三、什么是索引

索引可以理解为:

为数据库中的数据建立一个更快的查找结构。

如果没有索引:

可能需要一行一行扫描

如果有合适索引:

可以快速定位目标范围

四、索引类似什么

可以类比:

一本书的目录

没有目录:

从第一页翻到最后一页

有目录:

先找到章节位置

五、索引不是越多越好

索引优点:

加快查询
加快排序
加快分组
帮助 JOIN

索引缺点:

占磁盘空间

INSERT 更慢

UPDATE 更慢

DELETE 更慢

维护成本增加

六、MySQL 常见索引类型

常见:

PRIMARY KEY
主键索引

UNIQUE
唯一索引

INDEX
普通索引

FULLTEXT
全文索引

从数据结构角度还常见:

B+Tree

Hash

InnoDB 最核心的是:

B+Tree 索引

七、创建普通索引

CREATE INDEX idx_student_name
ON student(name);

八、创建唯一索引

CREATE UNIQUE INDEX uk_student_no
ON student(student_no);

九、查看索引

SHOW INDEX
FROM student;

十、删除索引

DROP INDEX idx_student_name
ON student;

十一、为什么 InnoDB 常用 B+Tree

B+Tree 适合:

等值查询

范围查询

排序

前缀匹配

磁盘存储

而且树高度通常较低:

减少磁盘 IO

十二、B+Tree 简单理解

结构:

根节点
↓
中间节点
↓
叶子节点

真正数据索引项主要集中在:

叶子节点

叶子节点之间还有:

有序链表

因此非常适合:

范围查询

十三、InnoDB 主键索引

InnoDB 表数据本身按照:

主键索引

组织。

这叫:

聚簇索引
Clustered Index

十四、聚簇索引

InnoDB 主键索引叶子节点:

直接保存整行数据

所以:

主键查询通常非常高效

十五、二级索引

例如:

CREATE INDEX idx_name
ON student(name);

这个索引叫:

二级索引
Secondary Index

它的叶子节点一般保存:

索引列
+
主键值

十六、什么是回表

例如:

SELECT *
FROM student
WHERE name = '张三';

如果使用:

idx_name

先找到:

name 对应主键

再根据主键去:

聚簇索引

拿完整行。

这个过程叫:

回表

十七、覆盖索引

如果查询字段全部都能从索引中拿到:

不用回表

称为:

覆盖索引

例如联合索引:

CREATE INDEX idx_name_age
ON student(name, age);

查询:

SELECT name, age
FROM student
WHERE name = '张三';

可能直接从索引完成。


十八、为什么覆盖索引更快

因为:

少一次主键查找
少一次磁盘访问

十九、联合索引

联合索引:

一个索引包含多个列

例如:

CREATE INDEX idx_major_age_score
ON student(
    major,
    age,
    score
);

二十、联合索引的排序规则

可以简单理解:

先按 major 排

major 相同
再按 age 排

major 和 age 都相同
再按 score 排

二十一、最左前缀原则

联合索引:

(a, b, c)

常见可利用:

a

a,b

a,b,c

不一定能完整利用:

b

c

b,c

二十二、为什么叫最左前缀

因为 B+Tree 是按照:

从最左列开始建立有序关系

如果跳过:

第一列

后续列整体上:

不再保持全局有序

二十三、最左前缀例子

索引:

CREATE INDEX idx_name_age_major
ON student(name, age, major);

可以:

WHERE name = '张三'

可以:

WHERE name = '张三'
AND age = 20

可以:

WHERE name = '张三'
AND age = 20
AND major = '软件工程'

二十四、SQL 条件书写顺序不等于索引顺序

例如索引:

(name, age)

SQL:

WHERE age = 20
AND name = '张三';

优化器通常可以:

重新分析条件

所以不是要求:

WHERE 必须按索引列顺序写

真正关键的是:

条件里是否包含最左列

二十五、范围查询对联合索引的影响

例如索引:

(a, b, c)

查询:

WHERE a = 1
AND b > 10
AND c = 5;

通常:

a
b

可以很好利用。

到了:

b 范围条件

后面 c 的索引定位能力往往会受到限制。


二十六、常见范围条件

例如:

>
<
>=
<=
BETWEEN
LIKE 'abc%'

二十七、LIKE 和索引

容易使用索引:

WHERE name LIKE '张%';

因为:

前缀确定

二十八、前导百分号

通常不利于普通 B+Tree 索引:

WHERE name LIKE '%张';

或者:

WHERE name LIKE '%张%';

因为:

无法从索引有序前缀快速定位

二十九、索引选择性是什么

选择性:

某一列区分数据的能力

可以近似理解:

不同值数量
/
总行数

三十、高选择性

例如:

身份证号
手机号
用户 ID
订单号

通常:

重复很少

选择性高。


三十一、低选择性

例如:

性别

是否删除

状态只有 0/1

值种类很少。

选择性低。


三十二、为什么选择性影响索引

如果:

WHERE gender = '男'

结果匹配:

全表 50%

数据库可能认为:

走索引 + 大量回表

反而不如:

全表扫描

所以即使有索引:

优化器也可能不用

三十三、主键为什么选择性高

主键要求:

唯一

因此:

选择性非常高

这也是主键查询效率高的重要原因之一。


三十四、索引选择性不是唯一标准

是否建索引还要考虑:

查询频率

是否参与 WHERE

是否参与 JOIN

是否参与 ORDER BY

是否参与 GROUP BY

更新频率

数据量

三十五、索引失效是什么意思

索引存在:

不代表每次 SQL 都会使用

如果优化器认为:

使用索引成本更高

或者 SQL 写法:

无法利用索引结构

就可能不使用。


三十六、常见索引失效:函数操作

例如索引:

create_time

不推荐:

WHERE DATE(create_time)
    = '2026-09-10';

因为:

对索引列做函数运算

可能无法直接按原值查索引。


三十七、更推荐范围写法

WHERE create_time
      >= '2026-09-10 00:00:00'

AND create_time
      < '2026-09-11 00:00:00';

三十八、常见索引失效:计算

例如:

WHERE age + 1 = 21;

不如:

WHERE age = 20;

三十九、常见索引失效:隐式类型转换

如果字段:

phone VARCHAR

却写:

WHERE phone = 13800138000;

数据库可能发生:

类型转换

更推荐:

WHERE phone = '13800138000';

四十、常见索引失效:前导模糊匹配

LIKE '%java%'

普通 B+Tree 通常不好利用。


四十一、常见索引失效:不合理 OR

例如:

WHERE name = '张三'
OR age = 20;

如果:

name 有索引

age 没索引

最终是否走索引:

取决于优化器成本判断

不能简单死记:

OR 一定索引失效

四十二、不要背“绝对索引失效规则”

MySQL 有:

查询优化器

最终是否使用索引:

由执行计划和成本决定

所以正确习惯:

写完 SQL
↓
EXPLAIN

四十三、什么是 EXPLAIN

EXPLAIN 用来查看:

SQL 执行计划

例如:

EXPLAIN
SELECT *
FROM student
WHERE student_no = '20260001';

四十四、EXPLAIN 重点字段

初学重点看:

type

possible_keys

key

key_len

rows

filtered

Extra

四十五、possible_keys

表示:

理论上可能使用哪些索引

不代表:

最终一定使用

四十六、key

表示:

实际选择的索引

如果:

NULL

说明:

没有使用索引

四十七、rows

表示:

优化器估计要扫描多少行

一般:

越少越好

但它是:

估算值

四十八、type

type 是非常重要的访问类型。

常见从好到差大致:

system

const

eq_ref

ref

range

index

ALL

四十九、const

例如:

主键 = 常量
唯一索引 = 常量

通常非常高效。


五十、ref

例如:

普通索引等值查询

可能出现:

ref

五十一、range

例如:

WHERE age BETWEEN 18 AND 22;

索引范围扫描。


五十二、index

表示:

扫描整个索引

虽然比 ALL 有时好一点,

但仍可能扫描很多。


五十三、ALL

表示:

全表扫描

如果大表出现:

ALL

通常需要重点关注。

但小表全表扫描:

不一定是问题

五十四、Extra 常见值

例如:

Using index

Using where

Using filesort

Using temporary

五十五、Using index

通常表示:

覆盖索引

可能不需要回表。


五十六、Using filesort

表示:

需要额外排序

不一定真的写磁盘文件,

但说明:

不能直接完全利用索引顺序

五十七、Using temporary

表示:

可能使用临时表

常见于:

复杂 GROUP BY

DISTINCT

排序

五十八、EXPLAIN 的正确使用方式

不要:

只看 key 不为 NULL

还要综合:

type

rows

Extra

实际数据量

业务响应时间

五十九、什么是事务

事务:

一组数据库操作,要么全部成功,要么全部失败。

例如转账:

A 扣 100
B 加 100

不能出现:

A 扣成功
B 加失败

六十、事务 ACID

事务四大特性:

A
Atomicity
原子性

C
Consistency
一致性

I
Isolation
隔离性

D
Durability
持久性

六十一、原子性

一组操作:

不可再分

要么:

全部提交

要么:

全部回滚

六十二、一致性

事务前后:

业务规则保持正确

例如转账前:

总金额 1000

转账后:

总金额仍应 1000

六十三、隔离性

多个事务并发执行时:

尽量互不干扰

六十四、持久性

事务一旦提交:

结果应该被持久保存

即使数据库随后崩溃:

已提交数据也应该尽可能恢复

六十五、事务基本语法

START TRANSACTION;

UPDATE account
SET balance = balance - 100
WHERE id = 1;

UPDATE account
SET balance = balance + 100
WHERE id = 2;

COMMIT;

异常:

ROLLBACK;

六十六、事务什么时候失效

常见原因包括:

没有使用支持事务的存储引擎

连接开启了自动提交

多个操作不在同一个事务/连接中

中途手动提交

DDL 带来特殊提交行为

应用层事务边界错误

六十七、MyBatis 事务失效案例

如果:

操作 A
使用 SqlSession 1

操作 B
使用 SqlSession 2

即使它们在一个 Service 方法里:

也可能不是同一个数据库事务

六十八、事务必须基于同一连接上下文

事务本质上和:

数据库连接

强相关。

所以:

同一个业务事务

通常应该:

使用同一个连接 / SqlSession

六十九、事务并发问题

常见:

脏读

不可重复读

幻读

七十、脏读

事务 A:

修改数据
但还没提交

事务 B:

读到了这个未提交数据

之后 A:

回滚

B 读到的就是:

脏数据

七十一、不可重复读

事务 A:

第一次查询余额 = 100

事务 B:

修改余额为 200
并提交

事务 A:

第二次查询余额 = 200

同一事务两次读:

结果不同

七十二、幻读

事务 A:

查询年龄 >= 18
共 10 条

事务 B:

插入一条满足条件的数据
并提交

事务 A:

再次范围操作时
发现像“多出一条”

这叫:

幻读

七十三、四种隔离级别

SQL 标准:

READ UNCOMMITTED

READ COMMITTED

REPEATABLE READ

SERIALIZABLE

七十四、READ UNCOMMITTED

允许:

读未提交

隔离最弱。

可能:

脏读
不可重复读
幻读

七十五、READ COMMITTED

只能看到:

已经提交的数据

解决:

脏读

但可能:

不可重复读
幻读

七十六、REPEATABLE READ

同一事务中:

多次一致性读取
通常保持一致快照

MySQL InnoDB 默认常见隔离级别:

REPEATABLE READ

七十七、SERIALIZABLE

隔离最强:

事务趋向串行化

并发能力最低。


七十八、隔离级别不是越高越好

隔离越高:

并发能力可能越差

需要平衡:

一致性
性能
并发

七十九、查看隔离级别

可以查看:

SELECT @@transaction_isolation;

八十、MVCC 是什么

MVCC:

Multi-Version Concurrency Control

中文:

多版本并发控制

作用:

让读操作在很多情况下不需要和写操作互相阻塞。


八十一、MVCC 核心思想

一条数据可能存在:

多个历史版本

事务读取时:

根据自己的可见性规则
选择一个版本

八十二、MVCC 和 undo log

InnoDB 会通过:

undo log

保存:

旧版本信息

用于:

回滚
MVCC

八十三、Read View 简单理解

事务进行一致性读时:

会根据一个“可见性视图”

判断:

某个版本自己能不能看

这个概念叫:

Read View

八十四、当前读和快照读

快照读:

普通 SELECT

常通过:

MVCC

读取历史可见版本。


八十五、当前读

例如:

SELECT ...
FOR UPDATE;

以及:

UPDATE
DELETE
INSERT

通常需要:

读取最新版本
并参与锁竞争

八十六、什么是锁

锁用于:

并发控制

防止多个事务:

同时修改同一资源

造成数据错误。


八十七、共享锁和排他锁

常见:

S Lock
共享锁

X Lock
排他锁

八十八、共享锁

多个事务可以:

同时持有共享锁

主要用于:

读取保护

八十九、排他锁

一个事务持有排他锁时:

其他事务通常不能再获得冲突锁

常见于:

UPDATE
DELETE

九十、行锁

InnoDB 常见:

行级锁

优点:

锁粒度小
并发高

九十一、表锁

锁整张表:

粒度大
并发低

某些操作和引擎会使用。


九十二、行锁不等于永远只锁一行

如果 SQL:

没有合适索引

锁定范围可能:

变大

因此:

索引设计

也会影响:

锁竞争

九十三、记录锁

Record Lock:

锁住具体索引记录

九十四、间隙锁

Gap Lock:

锁住索引记录之间的间隙

主要用来:

减少并发插入造成的幻读问题

九十五、Next-Key Lock

可以简单理解:

记录锁
+
间隙锁

组成一个范围锁定效果。


九十六、SELECT … FOR UPDATE

例如:

SELECT balance
FROM account
WHERE id = 1
FOR UPDATE;

表示:

当前读
并申请排他性质的锁

常用于:

库存扣减
余额修改
关键业务并发控制

九十七、FOR UPDATE 必须在事务中理解

如果:

自动提交立即结束

锁的意义可能很短。

通常:

START TRANSACTION
↓
SELECT ... FOR UPDATE
↓
业务操作
↓
COMMIT

九十八、悲观锁

悲观锁思想:

我认为并发冲突很可能发生
先加锁
再处理

例如:

SELECT ...
FOR UPDATE;

九十九、乐观锁

乐观锁思想:

默认别人不会冲突
提交更新时再检查版本

常见:

version 字段

一百、乐观锁 SQL

表:

id
stock
version

更新:

UPDATE product

SET
    stock = stock - 1,
    version = version + 1

WHERE
    id = 1
    AND version = 5;

如果返回:

0 行

说明:

版本冲突

一百零一、悲观锁和乐观锁

悲观锁:

冲突高
强控制
数据库锁

乐观锁:

冲突低
通过版本号检测

一百零二、什么是死锁

事务 A:

已经锁住资源 1
等待资源 2

事务 B:

已经锁住资源 2
等待资源 1

两边:

互相等

形成:

死锁

一百零三、死锁示例

事务 A:

锁用户 1
↓
再锁用户 2

事务 B:

锁用户 2
↓
再锁用户 1

可能形成死锁。


一百零四、减少死锁的方法

固定访问顺序

事务尽量短

减少一次事务处理的数据量

使用合适索引

避免无意义大范围锁

发生死锁后允许应用重试

一百零五、为什么事务要短

事务越长:

锁持有时间越长

导致:

阻塞更多
死锁概率更高
吞吐下降

一百零六、什么是 JOIN

JOIN:

把多张表按照关联条件连接起来查询

例如:

student

class

学生表里:

class_id

班级表:

id

一百零七、INNER JOIN

只返回:

两边都匹配的数据
SELECT
    s.name,
    c.class_name

FROM student s

INNER JOIN class c
    ON s.class_id = c.id;

一百零八、LEFT JOIN

返回:

左表全部数据
+
右表匹配数据

右表没有:

NULL

一百零九、LEFT JOIN 示例

SELECT
    s.name,
    c.class_name

FROM student s

LEFT JOIN class c
    ON s.class_id = c.id;

即使学生:

没有班级

也会保留学生。


一百一十、RIGHT JOIN

返回:

右表全部
+
左表匹配

实际项目通常:

LEFT JOIN 更常见

因为可以通过:

交换表顺序

避免大量 RIGHT JOIN。


一百一十一、JOIN 的 ON

ON s.class_id = c.id

表示:

表之间如何关联

一百一十二、ON 和 WHERE 区别

ON:

决定表如何连接

WHERE:

连接结果再做过滤

在 OUTER JOIN 中:

条件放 ON 还是 WHERE
可能影响最终结果

一百一十三、LEFT JOIN 最常见坑

例如:

SELECT *
FROM student s

LEFT JOIN class c
    ON s.class_id = c.id

WHERE c.status = 1;

由于 WHERE 要求:

c.status 必须有值

右表为空的行会被过滤。

效果可能变得接近:

INNER JOIN

一百一十四、如果希望保留左表全部

可以把右表过滤条件写进:

ON

例如:

LEFT JOIN class c
    ON s.class_id = c.id
    AND c.status = 1

一百一十五、JOIN 关联列为什么要建索引

例如:

student.class_id

class.id

如果关联列没有合适索引:

多表连接成本会明显增加

一百一十六、JOIN 优化原则

小结果集优先过滤

关联列建索引

避免 SELECT *

只查需要字段

减少不必要 JOIN

使用 EXPLAIN

一百一十七、GROUP BY

用于:

分组聚合

例如:

SELECT
    major,
    COUNT(*) AS student_count

FROM student

GROUP BY major;

一百一十八、HAVING

WHERE:

分组前过滤

HAVING:

分组后过滤

例如:

SELECT
    major,
    AVG(score) AS avg_score

FROM student

GROUP BY major

HAVING AVG(score) >= 80;

一百一十九、WHERE 和 HAVING 不要混淆

可以提前过滤的普通条件:

优先 WHERE

因为:

减少参与分组的数据

一百二十、ORDER BY 优化

如果排序列:

和查询条件匹配索引顺序

可能利用索引排序。

否则:

可能出现 Using filesort

一百二十一、GROUP BY 与索引

如果:

分组列顺序

与合适索引匹配,

可能减少:

额外排序和临时表

但最终仍应:

EXPLAIN 验证

一百二十二、SELECT * 为什么不推荐

SELECT *
FROM student;

问题:

读取无用列

网络传输更多

更容易回表

覆盖索引机会减少

表结构变化影响更大

一百二十三、只查需要字段

推荐:

SELECT
    id,
    student_no,
    name

FROM student;

一百二十四、分页性能问题

普通分页:

SELECT
    id,
    name

FROM student

ORDER BY id

LIMIT 100000, 20;

offset 很大时:

数据库仍可能跳过大量行

一百二十五、深分页优化思路

如果按主键连续翻页:

SELECT
    id,
    name

FROM student

WHERE id > 100000

ORDER BY id

LIMIT 20;

这叫:

基于游标 / Seek Pagination 的思想

一百二十六、深分页不是都能这样改

如果业务必须:

跳到第 5234 页

仍需要其他策略。

所以:

分页方案取决于业务

一百二十七、IN

例如:

WHERE id IN (
    1,
    2,
    3
);

少量值通常没问题。

但:

IN 列表巨大

会增加:

解析
优化
执行成本

一百二十八、NOT IN 和 NULL

这是经典坑。

如果子查询结果含:

NULL

NOT IN 结果可能:

与直觉不同

很多场景更适合:

NOT EXISTS

一百二十九、EXISTS

例如:

SELECT *
FROM student s

WHERE EXISTS (
    SELECT 1
    FROM score_record r
    WHERE r.student_id = s.id
);

表示:

只关心是否存在匹配行

一百三十、COUNT(*)

统计行数:

SELECT COUNT(*)
FROM student;

通常直接用:

COUNT(*)

不要为了“性能”随意改成:

COUNT(1)
COUNT(id)

真正差异要结合:

版本
执行计划
NULL 语义

理解。


一百三十一、COUNT(column)

COUNT(score)

只统计:

score 非 NULL 的行

这和:

COUNT(*)

语义不同。


一百三十二、存储引擎

MySQL 表可以使用不同:

Storage Engine

最常见:

InnoDB

历史上还常见:

MyISAM

一百三十三、为什么现在通常使用 InnoDB

InnoDB 支持:

事务

行锁

MVCC

外键

崩溃恢复

因此现代业务系统:

通常优先 InnoDB

一百三十四、查看表引擎

SHOW TABLE STATUS
LIKE 'student';

或者:

SHOW CREATE TABLE student;

一百三十五、主键为什么推荐短且稳定

InnoDB 二级索引叶子节点通常保存:

主键值

如果主键:

非常长

会让:

所有二级索引都更大

一百三十六、自增主键优点

短

顺序增长

插入位置相对集中

索引结构简单

因此很多业务表常用:

BIGINT AUTO_INCREMENT

一百三十七、UUID 作为主键的问题

字符串 UUID:

长度大

随机性强

B+Tree 插入位置分散

二级索引更大

所以不一定适合作为:

InnoDB 聚簇主键

一百三十八、但 UUID 不是绝对不能用

分布式场景:

可能需要全局唯一 ID

可以考虑:

有序 UUID

雪花 ID

业务 ID

BIGINT 分布式 ID

本质还是权衡。


一百三十九、字段类型也影响性能

例如年龄:

TINYINT / SMALLINT / INT

不要全部无脑:

BIGINT

一百四十、VARCHAR 长度设计

不要所有字符串:

VARCHAR(1000)

根据:

实际业务长度

设计。


一百四十一、金额类型

金额推荐:

DECIMAL

不要使用:

FLOAT / DOUBLE

存精确货币。


一百四十二、时间类型

常见:

DATETIME

TIMESTAMP

DATE

TIME

选择:

取决于业务语义

一百四十三、NULL 设计

是否允许 NULL:

要根据业务语义

不要因为:

怕 NULL

全部设置空字符串。

也不要:

随便允许所有字段 NULL

一百四十四、唯一约束

例如:

student_no

username

order_no

业务真正唯一时:

数据库应该建立 UNIQUE

不要只靠 Java:

先查询再判断

一百四十五、外键是否一定要用

数据库外键可以:

保证引用完整性

但部分互联网项目:

为了部署和高并发灵活性
可能选择逻辑外键

不能简单说:

外键一定好
或
外键一定不好

根据团队规范和业务决定。


一百四十六、SQL 优化第一原则

不要:

凭感觉优化

正确:

发现慢 SQL
↓
确认数据量
↓
EXPLAIN
↓
分析索引
↓
修改 SQL / 索引
↓
重新验证

一百四十七、不要先加一堆索引

错误:

查询慢
↓
每个字段都建索引

问题:

写入变慢

索引占空间

优化器选择更复杂

维护成本提高

一百四十八、慢 SQL 常见原因

没有合适索引

索引选择性低

查询返回太多数据

SELECT *

深分页

复杂 JOIN

大范围排序

大范围 GROUP BY

函数操作索引列

隐式类型转换

事务持锁时间过长

一百四十九、优化顺序

推荐:

1. 确认 SQL 是否合理

2. 确认返回字段是否过多

3. 确认过滤条件

4. 查看 EXPLAIN

5. 检查索引

6. 检查 JOIN

7. 检查排序/分组

8. 检查分页

9. 检查事务和锁

一百五十、索引设计原则

适合索引的列:

高频 WHERE

JOIN 关联列

ORDER BY

GROUP BY

高选择性列

不一定适合:

低选择性列

频繁更新列

很小的表

几乎不用查询的列

一百五十一、联合索引列顺序怎么考虑

考虑:

等值查询列

范围查询列

排序需求

分组需求

选择性

查询频率

不能只背:

选择性最高放最左

真实联合索引设计要结合:

完整 SQL 模式

一百五十二、一个联合索引例子

高频 SQL:

SELECT
    id,
    title,
    create_time

FROM article

WHERE
    user_id = ?

    AND status = ?

ORDER BY create_time DESC

LIMIT 20;

可能考虑联合索引:

(user_id, status, create_time)

因为:

前两列过滤
最后一列排序

一百五十三、为什么不能只看单列索引

如果分别建:

user_id

status

create_time

不一定比:

一个符合查询模式的联合索引

更好。


一百五十四、索引下推简单了解

Index Condition Pushdown:

ICP

简单理解:

尽量在索引层先过滤
减少回表

这是优化器能力的一部分。

当前:

理解概念即可

一百五十五、Change Buffer 简单了解

InnoDB 对某些二级索引修改:

可能暂时缓存修改

减少随机 IO。

当前阶段:

了解即可

一百五十六、Redo Log 简单了解

redo log:

记录数据页修改

主要服务:

事务持久性
崩溃恢复

一百五十七、Undo Log

undo log:

记录旧版本

服务:

事务回滚
MVCC

一百五十八、Binlog

binlog:

MySQL Server 层日志

常用于:

主从复制

数据恢复

一百五十九、为什么事务提交不是只写数据页

数据库为了:

性能
可靠性

会结合:

日志

内存缓冲

磁盘刷盘

共同实现持久化。


一百六十、死锁不是数据库崩了

出现死锁后:

InnoDB 通常会检测

并选择:

回滚一个事务

让其他事务继续。

应用程序应该:

正确处理异常
必要时重试

一百六十一、锁等待和死锁区别

锁等待:

A 等 B
但 B 最终会释放

死锁:

形成循环依赖
谁都无法继续

一百六十二、长事务风险

长事务可能:

长期持锁

undo log 积累

影响 MVCC 清理

增加死锁概率

影响并发性能

所以:

事务尽量短

一百六十三、事务里不要做慢外部调用

例如:

BEGIN
↓
UPDATE
↓
调用第三方 HTTP 10 秒
↓
UPDATE
↓
COMMIT

这 10 秒期间:

锁可能一直持有

非常危险。


一百六十四、事务中避免用户交互

例如:

开启事务
↓
等待用户输入验证码
↓
继续提交

完全不合理。


一百六十五、MyBatis + MySQL 事务关系

MyBatis:

sqlSession.commit();

底层还是:

数据库事务提交

MyBatis 只是帮我们:

管理 JDBC 连接和事务 API

一百六十六、以后 Spring 事务

Spring:

@Transactional

最终也还是:

管理数据库连接

begin

commit

rollback

只不过框架:

自动化了事务边界

一百六十七、练习题 1:普通索引

给:

student.name

创建索引。

使用:

SHOW INDEX

查看。


一百六十八、练习题 2:联合索引

创建:

(name, age, major)

分别测试:

name

name + age

age

major

然后:

EXPLAIN

观察差异。


一百六十九、练习题 3:LIKE

比较:

LIKE '张%'

LIKE '%张%'

执行计划差异。


一百七十、练习题 4:函数导致索引问题

给:

create_time

加索引。

比较:

DATE(create_time) = ...

和:

create_time >= ...
AND create_time < ...

一百七十一、练习题 5:EXPLAIN

分别观察:

type

key

rows

Extra

一百七十二、练习题 6:事务回滚

START TRANSACTION;

UPDATE ...

ROLLBACK;

观察数据是否恢复。


一百七十三、练习题 7:两个窗口模拟事务

打开两个 MySQL 会话:

Session A

Session B

测试:

一个事务修改不提交

另一个事务查询/修改

观察阻塞。


一百七十四、练习题 8:FOR UPDATE

事务 A:

START TRANSACTION;

SELECT *
FROM student
WHERE id = 1
FOR UPDATE;

事务 B:

UPDATE student
SET name = '测试'
WHERE id = 1;

观察:

锁等待

一百七十五、练习题 9:JOIN

建立:

student

class

分别练习:

INNER JOIN

LEFT JOIN

一百七十六、练习题 10:LEFT JOIN 条件位置

比较:

右表条件写 WHERE

右表条件写 ON

观察结果是否不同。


一百七十七、练习题 11:GROUP BY

统计:

每个专业人数

每个专业平均成绩

一百七十八、练习题 12:分页

准备:

大量测试数据

比较:

LIMIT 大 offset

WHERE id > ? LIMIT

理解深分页问题。


一百七十九、必须掌握的索引知识

B+Tree

聚簇索引

二级索引

回表

覆盖索引

联合索引

最左前缀

索引选择性

索引失效

EXPLAIN

一百八十、必须掌握事务知识

ACID

commit

rollback

隔离级别

脏读

不可重复读

幻读

MVCC

undo log

一百八十一、必须掌握锁知识

共享锁

排他锁

行锁

记录锁

间隙锁

Next-Key Lock

FOR UPDATE

悲观锁

乐观锁

死锁

一百八十二、必须掌握 JOIN

INNER JOIN

LEFT JOIN

RIGHT JOIN

ON

WHERE

一百八十三、必须掌握优化方法

EXPLAIN

索引

覆盖索引

减少 SELECT *

数据库分页

过滤尽量前置

JOIN 列建索引

事务尽量短

一百八十四、必须回答的问题

学完后应该能够回答:

1. 索引是什么?

2. InnoDB 为什么使用 B+Tree?

3. 什么是聚簇索引?

4. 什么是二级索引?

5. 什么是回表?

6. 什么是覆盖索引?

7. 什么是联合索引?

8. 什么是最左前缀?

9. 索引选择性是什么?

10. 为什么低选择性列可能不适合单独建索引?

11. 常见索引失效场景有哪些?

12. EXPLAIN 的 key/type/rows/Extra 分别怎么看?

13. 事务 ACID 是什么?

14. 四种隔离级别是什么?

15. 什么是脏读、不可重复读、幻读?

16. MVCC 是什么?

17. undo log 有什么作用?

18. 什么是共享锁和排他锁?

19. 什么是行锁?

20. 什么是间隙锁?

21. FOR UPDATE 是做什么的?

22. 乐观锁和悲观锁有什么区别?

23. 什么是死锁?

24. 如何减少死锁?

25. INNER JOIN 和 LEFT JOIN 有什么区别?

26. ON 和 WHERE 有什么区别?

27. 为什么分页应该尽量在数据库完成?

28. 什么是深分页?

29. 为什么 SELECT * 不推荐?

30. 为什么事务应该尽量短?

一百八十五、MySQL 进阶知识结构

MySQL 进阶
│
├─ 索引
│   ├─ B+Tree
│   ├─ 聚簇索引
│   ├─ 二级索引
│   ├─ 回表
│   ├─ 覆盖索引
│   ├─ 联合索引
│   ├─ 最左前缀
│   └─ 选择性
│
├─ 执行计划
│   ├─ type
│   ├─ key
│   ├─ rows
│   └─ Extra
│
├─ 事务
│   ├─ ACID
│   ├─ 隔离级别
│   ├─ MVCC
│   ├─ undo log
│   └─ redo log
│
├─ 锁
│   ├─ S/X
│   ├─ Record Lock
│   ├─ Gap Lock
│   ├─ Next-Key Lock
│   ├─ FOR UPDATE
│   └─ Deadlock
│
├─ JOIN
│   ├─ INNER
│   ├─ LEFT
│   └─ RIGHT
│
└─ SQL 优化
    ├─ 索引
    ├─ WHERE
    ├─ ORDER BY
    ├─ GROUP BY
    ├─ JOIN
    ├─ LIMIT
    └─ EXPLAIN

一百八十六、本章总结

MySQL 进阶最核心的是:

理解数据库为什么这样执行

而不是只会:

背 SQL 语法

索引核心:

B+Tree

联合索引

最左前缀

选择性

覆盖索引

回表

SQL 是否真的使用索引:

不能只靠猜

应该:

EXPLAIN

事务核心:

ACID

隔离级别

MVCC

commit / rollback

锁核心:

行锁

Record Lock

Gap Lock

Next-Key Lock

FOR UPDATE

死锁

JOIN 核心:

INNER JOIN
只要匹配

LEFT JOIN
保留左表全部

SQL 优化核心思路:

少查数据

少扫数据

减少回表

合理使用索引

避免深分页

减少大事务

减少锁竞争

到这里,你已经从:

“会写 MySQL”

进入:

“开始理解 MySQL 为什么快、为什么慢”

按照课程表,下一篇继续:

Maven / HTTP / Tomcat

会把 Java Web 工程构建、HTTP 协议和 Tomcat 运行机制重新系统串起来。