基础概念:
1.MySQL存储引擎有哪些?常用的InnoDB、MyISAM区别?
MySQL常见的存储引擎有InnoDB、MyISAM、Memory、Archive等。现在生产最常用的是InnoDB。
InnoDB:支持事务、行锁、MVCC、崩溃恢复、聚簇索引,适合高并发和事务场景。
MyISAM:不支持事务,不支持行锁,主要是表级锁、崩溃恢复能力弱,但结构简单
2.InnoDB相比MyISAM优势在哪?
InnoDB的优势主要有:
- 支持事务,满足ACID
- 支持行级锁,并发写能力更好
- 支持崩溃恢复,可靠性更高
- 支持MVCC,提高读写并发
- 支持外键
面试里可以直接说:InnoDB更适合现代业务系统,尤其是高并发、强一致、需要事务的场景
3.什么是事务?事务的四大特性ACID分别是什么?
事务是一组操作的集合,要么全部成功,要么全部失败
ACID分别是:
- A Atomicity 原子性:事务内操作要么全部成功,要么全部失败。主要靠undo log
- C Consistency 一致性:事务执行前后,数据从一个一致状态变成另一个一致状态
- I Isolation 隔离性:多个事务并发执行时,彼此影响,受隔离级别控制
- D Durability 持久性:事务提交后数据不会丢。主要靠redo log
4.数据库三范式是什么?日常开发一定要严格遵守吗?
第一范式:字段不可再分,保证原子性
第二范式:非主键字段必须完全依赖主键,避免部分依赖
第三范式:非主键字段不能依赖其他非主键字段,避免传递依赖
日常开发不一定要严格遵守。范式能减少冗余,提高一致性,但有时为了查询性能,会适当反范式,比如冗余用户名、订单快照、统计字段。
原则:核心数据保持一致,读多性能瓶颈场景可以有控制的冗余。
5.执行一条SQL语句,期间发生了哪些事情?
以更新语句为例:
- 客户端发送SQL
- Server层解析、优化、生成执行计划
- 执行层调用InnoDB
- InnoDB找到数据页,加载到Buffer Pool
- 记录undo log,便于回滚
- 修改内存页,产生脏页
- 写redo log prepare
- Server层写binlog
- redo log commit
- 后台线程择机把脏页刷盘
查询语句主要涉及解析、优化、执行、走索引、回表、返回结果
事务
1.MySQL四大事务隔离级别分别是什么?
- 读未提交 Read Uncommitted 允许读到别人未提交的数据
- 读已提交 Read Committed 只能读到其他事务已经提交的数据
- 可重复读 Repeatable-Read 同一个事务内,多次读取,读到事务启动那一刻的快照,结果一致
- 串性化 Serializable 最高隔离级别,串性执行,并发低
2.脏读、不可重复读、幻读分别是什么?
脏读:读到了其他事务还没有提交的数据。如果对方回滚,你读到的就是脏数据
不可重复读:同一个事务内两次读取同一行数据,结果不一样。通常是其他事务提交了修改
幻读:同一个事务内,两次范围查询,别的事务插入或删除数据并提交,前后返回行数不一样
脏读读到未提交、不可重复读是读同一行变了,幻读是结果集行数变了。
3.四大隔离级别分别能解决哪些问题?
- 读未提交:什么都解决不了
- 读已提交:解决脏读,但可能不可重复读和幻读
- 可重复读:解决脏读、不可重复读。InnoDB通过MVCC和临键锁在很多场景下也解决幻读
- 串性化:基本都能解决,但并发性能最低
生产中MySQL InnoDB默认是可重复读
4.InnoDB的默认级别是什么?
InnoDB的默认级别是可重复读
它通过MVCC保证普通快照的可重复读,通过next-key lock处理当前读下的幻读问题
5.什么是幻读,怎么解决幻读
幻读指一个事务内两次范围查询,第二次出现了第一次没有的记录,像"幻影"一样。
解决方式:
- 串行化隔离级别,直接强约束并发
- InnoDB可重复读下,普通快照读通过Read View避免幻读
- 当前读通过next-key lock锁住范围,阻止其他事务插入
注意:MVCC主要解决快照读的一致性,临键锁主要解决当前读的幻读
6.事务的实现原理:MVCC是什么?
事务能力不是一个单独机制完成的,而是多个机制配合:
- 原子性靠 undo log
- 持久性靠 redo log
- 隔离性靠锁和MVCC
- 一致性由业务约束、数据库约束和上述机制共同保证
MVCC是多版本并发控制。它让读操作可以读到某个时间点的数据版本,而不是总被写操作阻塞
通俗的说:数据被修改后,旧版本不会立即消失,读事务可以根据规则找到自己应该看到的版本。
7.可重复读隔离级别,完成解决幻读了吗?
对于普通select快照读,可重复读通过MVCC的Read View能保证同一事务内查询结果一致,基本避免幻读
对于select...for update、update、delete这类当前读,InnoDB通过next-key lock锁范围,防止其他事务插入,从而避免幻读
MVCC
1.MVCC底层原理是什么?
MVCC全称Multi-Version Concurrency Control,多版本并发控制。
InnoDB 每行记录都有隐藏字段,配合undo log保持历史版本,再通过Read View判断当前事务能看到哪个版本。
它的核心目标是提高读写并发:读不阻塞写,写不阻塞普通读。
2.什么是快照读,当前读?举例说明
快照读读取的是某个时间点的数据版本,不加锁。
普通select通常是快照读
当前读读取的是最新数据,并且通常要加锁
select * from user where id=1 for update;
update user set name='tom' where id=1;
delete from user where id=1;
快照读看历史版本,当前读看最新版本
3.隐藏字段DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID作用?
InnoDB行记录里有几个隐藏字段:
DB_TRX_ID:最近一次修改这行记录的事务id
DB_ROLL_PTR:回滚指针,指向undo log 中的旧版本
DB_ROW_ID:如果表没有主键和唯一非空索引,InnoDB会生成隐藏行id
MVCC主要依靠DB_TRX_ID、DB_ROLL_PTR找到可见版本。
4.undo log日志作用、版本链是什么?
undo log记录数据被修改前的旧值,用于事务回滚和给MVCC提供历史版本。
当一行数据被多次修改时,每次修改都会产生undo log,记录之间通过回滚指针串起来,就形成版本链。
查询时,如果当前版本对事务不可见,InnoDB会沿着版本链往前找,直到找到一个可见版本,或者找不到。
5.Read View视图四个字段作用、可见性规则?
Read View是MVCC判断版本是否可见的核心
四个字段:
- m_ids:创建Read View时,系统中活跃事务id列表
- min_trx_id:活跃事务中最小id
- max_trx_id:下一个将要分配的事务id
- creator_trx_id:创建这个Read View的事务id
可见性规则简化理解:
- 如果版本的事务id小于min_trx_id,说明早就提高了,可见
- 如果版本的事务id大于等于max_trx_id,说明是之后才出现的,不可见
- 如果事务id在m_ids里,说明当时还没有提交,不可见
- 如果事务id不在m_ids里,说明当时已经提交,可见
6.MVCC怎么实现可重复读、读已提交?
读已提交RC:每次执行普通select都会生成新的Read View。所以同一事务里,第二次查询能看到其他事务已经提交的数据
可重复读RR:事务中第一次普通select时生成Read View,后续普通select复用这个Read View。所以同一事务里多次读取结果一致
区别在于Read View的创建时机
索引
1.InnoDB索引结构为什么选B+树,不选二叉树、红黑树、B树?
数据库索引存在磁盘上,核心目标是减少磁盘IO。
二叉树和红黑树高度相对较高,数据量大时查找层数多,磁盘IO多
B树每个节点既存key也存数据,单页能放的key数量相对少,树可能更高。
B+树非叶子节点只存key和指针,叶子节点存完整数据或者主键,单页能放更多key。树更矮,IO更少。叶子节点还用链表连接,范围查询更方便
所以InnoDB选择B+树是为了降低IO、提高范围查询效率
2.B+树和B树的区别?
B树的每个节点都可以存数据
B+树只有叶子节点存数据,非叶子节点只做索引导航
B+树叶子节点之间有链表,范围查询更快
B+树单个非叶子节点能存更多key,树更矮,磁盘IO更少
3.聚簇索引和非聚簇索引(二级索引)区别?
聚簇索引的叶子节点存整行数据。在InnoDB中,主键索引就是聚簇索引
非聚簇索引,也叫二级索引,叶子节点存的是索引字段和主键值。
通过二级索引查到主键后,如果还需要其他字段,就要再根据主键回到聚簇索引查整行,这就是回表。
4.主键索引、唯一索引、普通索引、联合索引区别?
主键索引:唯一且不能为空,一张表只能有一个主键
唯一索引:值不能重复,但通常允许null,具体行为和数据库规则有关
普通索引:没有唯一性限制,只提升查询效率
联合索引:多个字段组合成一个索引,比如(a,b,c),使用时要遵循最左前缀原则
5.什么是回表查询?怎么避免回表?
回表是指通过二级索引查到主键后,在根据主键去聚簇索引查整行数据。
假如有索引name:
select age from user where name='tom';
如果age不在索引里,就需要回表
避免回表的方式是使用覆盖索引,把查询需要的字段都放进索引:
creat index idx_name_age on user(name,age);
6.覆盖索引是什么?使用场景?
覆盖索引是指查询需要的字段都能从索引里拿到,不需要回表
例如联合索引(name,age)
select name,age from user where name='tom';
这种情况下只查索引就够了
覆盖索引适合高频查询、列表页、分页查询等场景,可以减少回表IO
7.最左前缀原则原理,为什么要遵循?
联合索引按字段顺序排序,比如(a,b,c)索引先按a排,再按b排,最后按c排
所以查询必须从最左字段开始连续使用,才能充分利用索引。
可以走索引:
where a=1
where a=1 and b=2
where a=1 and b=2 and c=3
不可以:
where b=2
where c=3
因为缺少最左字段a,索引整体顺序用不上
8.索引下推原理和作用?
索引下推Index Condition Pushdown,简称ICP,是MySQL的一种优化
没有索引下推时,存储引擎根据索引找到记录后,可能先回表,再由Server层判断其他条件
有索引下推时,能在存储引擎层先用索引里的字段过滤一部分数据,减少回表次数
典型场景是联合索引中部分字段可以用于过滤,但不完全用于定位。
9.什么是索引失效?哪些情况会导致索引失效?
索引失效是指SQL虽然有索引,但优化器没有使用,或者只能使用一部分。
常见原因:
- 对索引列使用函数或表达式
- 字段类型不一致导致隐式转换
- like '%xxx'前缀模糊
- 联合索引不满足最左前缀
- 使用or且部分条件没有索引
- 范围查询后面的联合索引字段无法继续有序利用
- 数据量太小或优化器判断全表扫描更划算
优化时不要只看建没建索引,要看explain里实际有没有用。
10.模糊查询like '%xxx'、like 'xxx%'哪个走索引
like '%xxx'一般不能走普通B+树索引,因为开头不确定,无法从索引树定位范围
like 'xxx%'可以走索引,因为前缀确认,B+树可以按范围查询
如果必须支持任意位置模糊搜索,可以考虑全文索引、搜索引擎,或者业务测倒排索引
11.字段类型隐式转换为什么会导致索引失效?
如果字段类型和查询条件类型不一致,MySQL可能对字段做隐式转换
例如手机号字段是varchar,却这样查:
where phone=13456789001
MySQL可能把phone转成数字比较,相当于对索引列做函数处理,索引就可能失效。
正确写法:where phone='13456789001'
12.为什么不建议用select *?
原因:
- 查出不需要的字段,增加网络和内存开销
- 更容易回表,无法利用覆盖索引
- 表结构变更时,结果字段不稳定
- 大字段如text、blob会拖慢查询
生产建议明确写出需要的字段
13.联合索引创建顺序原则:区分度高、长度小、经常查询
联合索引字段顺序一般考虑:
- 经常用于查询条件的字段靠前
- 区分度高的字段优先
- 字段长度小的优先,索引更紧凑
- 等值查询字段通常放前面,范围查询字段放后面
- 还要兼顾order by、group by
14.什么时候不适合建索引?
- 表数据量很小
- 字段区分度很低,比如性别、状态值很少
- 字段很少用于查询条件
- 写入非常频繁,索引会增加维护成本
- 大字段不适合直接建普通索引
- 已有联合索引可以直接覆盖,不需要重复建单列索引
索引不是越多越好,它能加快查询,但会拖慢写入,并占用磁盘空间
锁
1.InnoDB有哪些锁?行锁、表锁、意向锁
常见锁:
- 表锁:锁整张表
- 行锁:锁某些记录,粒度小,并发度高
- 意向锁:表级锁,用来表示事务接下来想锁某些行
- 记录锁:锁具体索引记录
- 间隙锁:锁两个索引记录之间的间隙
- 临键锁:记录锁+间隙锁
InnoDB的行锁是加在索引上的,如果查询条件没有走索引,可能导致锁范围变大
2.行锁什么时候变表锁?
严格说,InnoDB行锁不会真正升级为表锁;但如果SQL没有走索引,InnoDB可能扫描很多行并对大量记录加锁,看起来像锁表
常见原因:
- where条件没有索引
- 索引失效
- 字段类型不一致导致隐式转换
- 范围条件过大
所以更新和删除时一定要确认条件走索引,尤其是大表
3.记录锁,间隙锁,临键锁分别是什么?
记录锁Record Lock:锁住某一条索引记录
间隙锁Gap Lock:锁住索引记录之间的间隙,不锁具体记录,主要防止幻读
临键锁Next-Key Lock:记录锁+间隙锁,既锁记录,也锁记录前的间隙
例如索引里有10和20,间隙锁可能锁住(10,20)这个范围,防止其他事务插入15
4.临键锁怎么解决幻读?
幻读是同一个事务内,两次范围查询结果条数不一致。
在可重复读隔离级别下,InnoDB对范围查询加临键锁,锁住已有记录和记录之间的间隙、这样其他事务就不能在这个范围里插入新记录
所以临键锁通过"锁记录+锁间隙"防止范围查询内新增数据,从而解决当前读下的幻读问题
5.什么是死锁?产生条件、怎么排查和避免死锁?
死锁是多个事务互相等待对方持有的锁,导致都无法继续
产生条件:
- 互斥
- 持有并等待
- 不可剥夺
- 循环等待
排查方式:
- 使用show engine innodb status查看最近一次死锁信息
- 查看事务持有什么锁,等待什么锁
- 结合慢SQL、业务日志定位SQL顺序
避免方式:
- 统一加锁顺序
- 事务尽量短
- where条件走索引
- 避免大范围更新
- 必要时使用重试机制
日志
1.MySQL三大日志:redo log、undo log、binlog各自作用?
redo log是InnoDB的重做日志,主要保证事务的持久性。事务提交后,即使数据库突然宕机,也可以通过redo log把已经提交的数据恢复过来
undo log是回滚日志,主要保证事务原子性,也用于MVCC。事务执行过程中会记录修改前的数据,如果事务回滚,就可以根据undo log恢复旧值
binlog是MySQL Server层的二进制日志,主要用于主从复制、数据恢复和审计。它记录的是数据库发生了哪些逻辑变更。
- redo log:保证崩溃恢复,偏物理
- undo log:保证事务回滚和MVCC,记录久版本
- binlog:保证复制和归档,偏逻辑
2.redo log 为什么能保证事务崩溃恢复?WAL机制是什么
redo log 能保证事务崩溃恢复,核心靠WAL机制,也就是Write Ahead Logging,先写日志,再写数据页。
InnoDB修改数据时,不会每次都立刻把磁盘上的数据页改掉,而是先修改内存中的Buffer Pool,同时记录redo log。事务提交时,只有redo log持久化成功,就认为事务提交成功
如果数据库宕机,内存里的脏页可能还没有刷到磁盘,但redo log已经在磁盘上了,重启后InnoDB会根据redo log重放修改,把数据恢复到提交后的状态
3.binlog是什么?statement、row、mixed格式区别
binlog是MySQL Server层的二进制日志,记录数据库变更,常用于主从复制和数据恢复
它有三种常见格式:
- statement:记录SQL语句。优点是日志量少,缺点是某些SQL在主从执行结果可能不一致,比如now()、uuid()不确定数据的更新
- row:记录每一行数据的变化。优点是最准确,主从一致性更好;缺点是日志量可能很大
- mixed:混合模式。MySQL会根据SQL是否安全,自动选择statement或row
生产环境更常用row,因为复制更可靠,也方便做数据订正和恢复
4.redo log和binlog区别
redo log和binlog经常一起问,因为它们都记录修改,但定位完全不同。
- redo log是InnoDB引擎层日志,binlog是MySQL Server层日志
- redo log主要用于崩溃恢复,binlog主要用于主从复制和数据恢复
- redo log是循环写,空间固定,会覆盖旧日志;binlog是追加写,一个文件写满后切换到另一个
- redo log记录偏物理变化,比如某个页做了什么修改;binlog记录偏逻辑变化,比如执行了什么SQL或哪行变成什么样
redo log管"宕机后自己怎么恢复",binlog管"别人怎么同步和回放"
5.事务提交时redo log和binlog两阶段提交原理
两阶段提交是为了保证redo log和binlog一致
大致流程:
- InnoDB写redo log,状态prepare
- MySQL Server写binlog
- InnoDB把redo log改成commit状态
事务提交涉及两个日志,如果只写成功一个就宕机,会出现主库和从库数据不一致
恢复时会判断:
- redo log有prepare,binlog也完整:提交事务
- redo log有prepare,binlog不完整:回滚事务
这样可以保证主库崩溃恢复结果和binlog复制结果一致
SQL优化
1.一条SQL执行完整流程
- 客户端发送SQL到MySQL Server
- 连接器管理连接和权限
- 解析器做词法、语法分析
- 预处理器检查表、字段是否存在
- 优化器选择执行计划,比如用哪个索引、表连接顺序
- 执行器调用存储引擎接口
- 存储引擎读取或修改数据
- 返回结果给客户端
如果更新语句,还会涉及undo log、redo log、log、binlog等日志
2.explain执行计划每个字段含义
explain用来查看SQL执行计划,面试常问这些字段:
- id:查询执行顺序标识。id越大通常越先执行;相同id从上往下执行
- select_type:查询类型,比如SIMPLE、PRIMARY、SUBQUERY、DERIVED
- type:访问类型,表示查表效率,优化重点字段
- key:实际使用的索引
- rows:优化器预估要扫描的行数
- Extra:额外信息,比如Using index、Using where、Using filesort、Using temporary
看explain时,重点关注type、key、rows、Extra。
3.type执行效率级别:all、index、range、ref、eq_ref、const、system
type表示MySQL怎么访问表,常见效率从差到好大概是
all<index<range<ref<eq_ref<const<system
all:全表扫描,通常最差
index:扫描整个索引树,比全表扫描稍好,但仍然扫很多
range:范围扫描,比如between、>、in
ref:普通索引等值匹配,可能匹配多行
eq_ref:唯一索引或主键关联查询,每次最多匹配一行
const:主键或唯一索引等值查询,结果最多一行
system:表只有一行,是const的特殊情况
实际优化目标一般是避免all,尽量达到range、ref或更好
4.怎么看慢查询日志?
慢查询日志用于记录执行时间超过阈值的SQL
常用参数:
- slow_query_log:是否开启慢查询日志
- long_query_time:超过多少秒算慢SQL
- slow_query_log_file:慢查询文件路径
排查时重点看:
- SQL原文
- 执行耗时
- 扫描行数
- 返回行数
- 是否走索引
常用分析工具有mysqldumpslow和pt-query-digest
5.慢查询优化整体思路
- 用慢查询日志定位问题SQL
- 用explain看执行计划
- 判断是否走了合适索引
- 检查是否有回表、Using filesort、Using temporary
- 优化SQL写法,减少扫描行数
- 必要时调整索引、拆表、缓存或改业务方案
核心原则:少扫行、少回表、少排序、少临时表
6.limit分页深偏移量怎么优化?例如limit 1000000,10
limit 1000000,10慢,是因为MySQL需要先扫描并丢弃前1000000行,再返回10行
常见优化方式:
第一种,基于上一页最大id做游标分页
select * from user where id>1000000 order by id limit 10;
第二种,先用覆盖索引查出id,再回表
select u.* from user u join( select id from user order by id liimit 1000000,10 ) t on u.id=t.id;
第三种,产品层面避免跳到特别深的页,比如搜索引擎通常只展示前几十页。
7.order by排序原理,什么时候Using filesort?怎么优化
order by如果能直接利用索引顺序,就不需要额外排序
如果不能利用索引排序,MySQL会使用filesort。这里的filesort不一定真的落磁盘,它表示额外排序算法,数据大时可能用临时文件
常见触发原因:
- 排序字段没有合适索引
- 联合索引顺序不符合最左前缀
- 排序方向混乱,索引无法完全利用
- where条件和order by字段不匹配
优化方式:
- 给where+order by建合适联合索引
- 尽量使用覆盖索引
- 控制返回数据量
- 避免对排序字段使用函数或表达式
8.group by原理与优化思路
group by用于分组聚合。MySQL执行时通常需要按分组字段聚集数据,可能用索引,也可能用临时表和排序
优化思路:
- 给分组字段建立索引
- where先过滤,减少参与分组的数据量
- 只查询必要字段
- 避免大结果集分组
- 能在业务或离线任务预聚合的,不要每次实时算大表
如果explain里出现Using temporary/Using filesort。说明可能存在额外临时表和排序成本
9.join连接原理:内连接、左连接、右连接
join本质是把多张表按条件关联起来
- 内连接inner join:只返回两边都匹配的数据
- 左连接left join:返回左表全部数据,右表匹配不到时右表字段为null
- 右连接right join:返回右表全部数据,左表匹配不到时左表字段为null
开发中更常用内连接和左连接。右连接通常可以改写成左连接,保持阅读习惯统一
10.大表join怎么优化?
大表join优化重点是减少驱动表数据量,并让被驱动表能走索引
常见做法:
- 小表驱动大表
- join字段建立索引,类型保持一致
- 先where过滤,再join
- 只查需要字段,避免select *
- 大分页、大排序、大分组尽量拆分
- 复杂场景可以用冗余字段、宽表、缓存、离线计算
让参与join的数据尽量少,让匹配过程尽量走索引
总结
- redo log保证崩溃恢复,undo log支持回滚和MVCC,binlog用于复制和归档
- WAL是先写日志再写数据页,保证宕机后能恢复
- 两阶段提交解决redo log和binlog一致性问题
- explain重点看type、key、rows、Extra
- 慢SQL优化核心是少扫行、少回表、少排序、少临时表
- InnoDB默认级别是可重复读
- MVCC依赖隐藏字段、undo log版本链和Read View
- 快照读读历史版本,当前读读最新版本并加锁
- InnoDB索引用B+树,是为了降低IO和优化范围查询
- 联合索引要遵循最左前缀原则
- like 'xxx%'通常可走索引,like '%xxx'通常不走普通索引
日志:redo、undo、binlog分别解决恢复、回滚、复制
SQL优化:先定位慢SQL,再看执行计划,最后减少扫描和回表
锁:理解行锁、间隙锁、临键锁以及死锁排查
事务:抓住ACID、隔离级别、脏读、不可重复读、幻读
MVCC:抓住隐藏字段、undo版本链、Read View
索引:抓住B+树、聚簇索引、回表、覆盖索引、最左前缀