背景与动机
Java 后端开发中,MySQL 是最常见的关系型数据库。面试和日常开发都会反复碰到:选 InnoDB 还是 MyISAM、事务隔离级别、索引为什么快、慢 SQL 怎么优化。本篇按「引擎 → 事务 → 索引 → 锁 → 优化」组织,便于和面试速查对照。
核心原理拆解
1. 存储引擎:MyISAM 与 InnoDB
| 维度 | MyISAM | InnoDB |
|---|---|---|
| 事务 | 不支持 | 支持 ACID |
| 锁粒度 | 表锁 | 行锁(也支持表锁) |
| 外键 | 不支持 | 支持 |
| 总行数 | 保存 | 不保存(需 count) |
| 文件 | .frm、.MYD、.MYI | 表空间(共享或独立) |
| 索引 | 非聚集索引 | 聚集索引(主键)+ 二级索引 |
选型:现代业务默认 InnoDB;只读、不要求事务的归档表才考虑 MyISAM。
2. MySQL 中的锁
| 类型 | 开销 | 加锁速度 | 死锁 | 粒度 | 并发 |
|---|---|---|---|---|---|
| 表锁 | 小 | 快 | 不会 | 大 | 低 |
| 行锁 | 大 | 慢 | 会 | 小 | 高 |
| 页锁 | 介于二者 | 介于二者 | 会 | 介于二者 | 一般 |
InnoDB 在可重复读下通过 MVCC + 行锁实现并发;锁优化思路:读写分离、缩小锁粒度、缩短持锁时间、多线程以相同顺序访问资源。
3. 事务与 ACID
事务是一组 SQL 的逻辑单元,要么全部成功提交,要么全部回滚。
- 原子性(A):全部执行或全部不执行
- 一致性(C):数据从一种合法状态到另一种合法状态
- 隔离性(I):并发事务互不干扰(由隔离级别控制)
- 持久性(D):提交后结果持久保存
默认 autocommit=1,每条语句自动提交。需要事务时:SET autocommit=0 或 START TRANSACTION,再 COMMIT / ROLLBACK。
4. 四种隔离级别
| 级别 | 说明 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|---|
| READ UNCOMMITTED | 读未提交 | 可能 | 可能 | 可能 |
| READ COMMITTED | 读已提交 | 否 | 可能 | 可能 |
| REPEATABLE READ | 可重复读(InnoDB 默认) | 否 | 否 | 理论上可能,InnoDB 用 MVCC+间隙锁缓解 |
| SERIALIZABLE | 串行化 | 否 | 否 | 否 |
5. 索引
索引是帮助 MySQL 高效获取数据的数据结构。InnoDB 默认用 B+ 树:
- 叶子节点存数据(聚集索引存整行,二级索引存主键值)
- 叶子节点之间有指针,适合范围查询
- 建议使用自增主键,减少页分裂
| 类型 | 说明 |
|---|---|
| 普通索引 | 加快查询,允许重复 |
| 唯一索引 | 列值唯一 |
| 主键索引 | 特殊的唯一索引,一张表一个 |
| 联合索引 | 多列组合,如 INDEX(a, b) |
代价:加快读,减慢写(维护索引);占磁盘空间。
索引失效常见情况:
LIKE '%xxx'左模糊OR两侧未同时走索引- 隐式类型转换(如 varchar 与数字比较)
- 对索引列做函数或运算
6. 连接查询
- 内连接:只返回两表匹配的行
- 左外连接:左表全保留,右表无匹配填 NULL
- 右外连接:右表全保留
- 自连接:表与自身连接
- 交叉连接:笛卡尔积,一般需加条件
7. 常用字段与类型
- CHAR vs VARCHAR:CHAR 定长、尾部空格处理;VARCHAR 变长,更省空间
- 金额:
DECIMAL(p,s),避免浮点误差 - 时间:
DATETIME(8 字节,与时区无关)、TIMESTAMP(4 字节,受时区影响,可自动更新)、DATE/TIME - 大文本:
TEXT(不区分大小写排序)vsBLOB(二进制)
8. SQL 分类
- DDL:
CREATE、ALTER、DROP - DML:
SELECT、INSERT、UPDATE、DELETE - DCL:
GRANT、REVOKE - DQL:查询(常归入 DML 讨论)
9. EXPLAIN 与慢 SQL 优化(面试必准备一个案例)
索引为什么快:B+ 树把「全表扫描」变成「按索引定位」,减少读取页数。
什么时候加索引:WHERE / JOIN / ORDER BY 里高频、区分度好的列;避免过多索引拖慢写入。
EXPLAIN 看什么(记 3 个就够):
| 列 | 关注点 |
|---|---|
type | 最好 ref/range,避免 ALL(全表扫描) |
key | 实际用到的索引 |
rows | 预估扫描行数,越小越好 |
话术模板(可套自己的项目):
列表接口变慢,EXPLAIN 看到 type=ALL、rows 很大,在 where 里的 tenant_id + create_time 上加了联合索引后,type 变为 range,接口从 3s 降到 300ms 以内。10. 实践优化顺序
- SQL 与索引优化(含 EXPLAIN)
- 表结构设计
- 数据库参数配置
- 硬件
常见手段:避免 SELECT *、减少不必要的 JOIN、读写分离、分表、缓存(Redis 等)、合理索引、字段类型尽量小。
11. SQL 注入与防护
原因:拼接 SQL 时未过滤特殊字符。防护:预编译(PreparedStatement)、参数校验、最小权限、避免暴露错误堆栈。
常见陷阱与错误示例
1. 在 InnoDB 表上使用 SELECT COUNT(*) 期望 O(1)
InnoDB 不保存总行数,COUNT(*) 需扫描索引或全表(视版本与优化器而定)。
2. 联合索引不满足最左前缀
索引 (a, b, c) 查询只有 b 条件时通常用不上该索引。
3. 大事务长时间持锁
拆小事务,避免在事务中做 RPC 或复杂计算。
面试高频问题
完整 40 道速查见 MySQL 面试题 40 道。
一句话总结
MySQL 面试主线:InnoDB + 事务隔离 + B+ 树索引 + 行锁;优化先 SQL 和索引,再架构(读写分离、分表、缓存);写操作注意索引维护成本与事务范围。