本文写于 2018 年 3 月——MySQL 5.7 主流,MySQL 8.0 即将发布(2018-04),InnoDB 全面替代 MyISAM。
一、事务基础特性 ACID
1.1 ACID 定义
| 特性 | 描述 | 实现 |
|---|---|---|
| A 原子性 | 一个事务中的操作要么全部成功,要么全部失败 | Undo Log 回滚日志 |
| C 一致性 | 数据库总是从一个一致性的状态转换到另外一个一致性的状态 | 应用层 + 完整性约束(主键/外键/唯一键) |
| I 隔离性 | 一个事务的修改在最终提交前,对其他事务是不可见的 | 锁 + MVCC |
| D 持久性 | 一旦事务提交,所做的修改就会永久保存到数据库中 | Redo Log |
1.2 ACID 靠什么保证
- A 原子性:由 undo log 日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的 sql
- C 一致性:一般由代码层面来保证
- I 隔离性:由 MVCC 来保证
- D 持久性:由内存 + redo log 来保证。mysql 修改数据同时在内存和 redo log 记录这次操作,事务提交的时候通过 redo log 刷盘,宕机的时候可以从 redo log 恢复
二、数据库三大范式
2.1 三范式定义
第一范式(1NF):确保每列保持原子性,列不可再分。如地址可以再分成省/市/区/街道。
第二范式(2NF):确保表中的每列都和主键相关,行可以唯一区分,主键约束。
第三范式(3NF):确保每列都和主键列直接相关(而不是间接相关),外键约束。这里可以反范式做冗余。
范式是一级一级依赖的。
2.2 面试追问
Q1:反范式是反什么?
反范式 = 故意冗余字段,用空间换查询性能。
典型反范式场景:
- 订单表冗余用户姓名(避免 JOIN 用户表)
- 评论表冗余文章标题(避免 JOIN 文章表)
- 汇总表冗余明细数据(避免 GROUP BY)
Q2:什么场景用范式?什么场景用反范式?
- OLTP(联机事务):用范式(写多读少,节省空间)
- OLAP(联机分析):用反范式(读多写少,提升查询速度)
三、InnoDB vs MyISAM
3.1 对比
| 维度 | InnoDB | MyISAM |
|---|---|---|
| 事务支持 | ✓ | ✗ |
| 锁机制 | 行级/表级锁 | 表级锁 |
| 外键约束 | ✓ | ✗ |
| 崩溃恢复 | 强大(Redo log + Undo log) | 无事务日志 |
| 存储结构 | 聚集索引(数据文件和索引绑定) | 非聚集索引(数据文件分离) |
| 性能特点 | 写操作效率高 | 低并发下简单查询快 |
3.2 选型建议
以前很多人推荐 MyISAM 的理由是"它只读性能好"或者"它支持全文索引(Full-text Index)"。但随着 MySQL 的演进,InnoDB 从 5.6 开始就已经支持了全文索引,并且在多核 CPU 和大内存的现代服务器上,InnoDB 的读写综合性能与并发吞吐量已经全面超越了 MyISAM。
- 100% 选择 InnoDB:绝大多数常规业务、高并发系统、需要保证数据不丢不脏的系统
- 极少数考虑 MyISAM:完全不需要事务、极高比例的纯读取、对服务器内存极其苛刻的小型只读报表、或是临时的数据分析物理表
四、事务隔离级别
4.1 四大隔离级别
| 隔离级别 | 描述 |
|---|---|
| 未提交读(READ UNCOMMITTED) | 事务中的修改,即使没有提交,对其它事务也是可见的 |
| 提交读(READ COMMITTED) | 一个事务只能读取已经提交的事务所做的修改 |
| 可重复读(REPEATABLE READ) | 保证在同一个事务中多次读取同样数据的结果是一样的(MySQL 默认) |
| 可串行化(SERIALIZABLE) | 强制事务串行执行 |
4.2 并发问题
| 隔离级别 | 脏读 | 不可重复读 | 幻影读 |
|---|---|---|---|
| 未提交读 | ✓ | ✓ | ✓ |
| 提交读 | ✗ | ✓ | ✓ |
| 可重复读 | ✗ | ✗ | ✓(MySQL InnoDB 通过间隙锁部分解决) |
| 可串行化 | ✗ | ✗ | ✗ |
4.3 MySQL 默认隔离级别
| |
五、SQL 优化的 19 条军规
5.1 索引相关(10 条)
对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描。
应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描。
应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描。
in 和 not in 也要慎用,否则会导致全表扫描。
like 的查询也可能导致全表扫描:若要提高效率,可以考虑全文检索。
如果在 where 子句中使用参数,也会导致全表扫描。因为 SQL 只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。
应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。
应尽量避免在 where 子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。
不要在 where 子句中的"=“左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
5.2 复合索引(1 条)
- 在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能地让字段顺序与索引顺序相一致。
5.3 写入相关(2 条)
不要写一些没有意义的查询,如需要生成一个空表结构。
Update 语句,如果只更改 1、2 个字段,不要 Update 全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志。
5.4 JOIN 相关(1 条)
- 对于多张大数据量(这里几百条就算大了)的表 JOIN,要先分页再 JOIN,否则逻辑读会很高,性能很差。
5.5 COUNT 相关(1 条)
select count(*) from table这样不带任何条件的 count 会引起全表扫描,并且没有任何业务意义,是一定要杜绝的。
5.6 索引数量(1 条)
- 索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引。一个表的索引数最好不要超过 6 个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
5.7 物理顺序(1 条)
- 应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
5.8 数据类型(2 条)
尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
尽可能的使用 varchar/nvarchar 代替 char/nchar,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
六、索引原理
6.1 B+ Tree 索引结构
InnoDB 默认使用 B+ Tree 索引:
| |
特点:
- 叶子节点才存储数据(聚簇索引)或主键(二级索引)
- 所有叶子节点形成有序链表(范围查询友好)
- 非叶子节点只存索引键
- 每个节点大小 = 16KB(默认 page size)
6.2 聚簇索引 vs 二级索引
| 维度 | 聚簇索引(Clustered) | 二级索引(Secondary) |
|---|---|---|
| 数量 | 每表 1 个 | 多个 |
| 数据存储 | 叶子节点存完整行数据 | 叶子节点存主键 |
| 查找二级索引 | 需要回表 | - |
6.3 EXPLAIN 关键字段
| |
| 字段 | 含义 |
|---|---|
| id | 查询序号 |
| select_type | SIMPLE / PRIMARY / SUBQUERY / DERIVED |
| type | system / const / eq_ref / ref / range / index / ALL(至少要 range) |
| possible_keys | 可能使用的索引 |
| key | 实际使用的索引 |
| key_len | 索引使用字节数 |
| rows | 预估扫描行数 |
| Extra | 额外信息(Using index / Using where / Using filesort / Using temporary) |
关键 type 取值:
| type | 含义 | 性能 |
|---|---|---|
| system | 表中只有一行 | 最优 |
| const | 主键 / 唯一索引等值 | 优 |
| eq_ref | JOIN 唯一索引 | 优 |
| ref | 非唯一索引等值 | 良 |
| range | 范围扫描 | 中 |
| index | 全索引扫描 | 差 |
| ALL | 全表扫描 | 极差 |
七、写在最后
MySQL 面试要点:
- 基础:ACID 实现原理、隔离级别、InnoDB vs MyISAM
- 索引:B+ Tree 结构、聚簇索引 vs 二级索引、Explain 字段
- 优化:19 条 SQL 优化军规、慢查询排查
- 进阶:MVCC 实现、锁机制(行锁 / 间隙锁 / 临键锁)
