1. 面试中被问到的问题
数据库中范式是什么?
设计数据库表时, 用来减少数据冗余,避免更新异常的一套规范
常见的有三范式:
第一范式: 字段不可再拆,保证原子性
第二范式: 非主键字段必须完全依赖主键
第三范式: 非主键字段不能依赖其它非主键字段
MySql的主键和外键分别是什么? 有什么区别?
主键是: 用来唯一标识一张表中的一行数据
外键是: 用来标识两张表之间的关联关系, 通常外键指向的是另一张表的主键
主键和外键的区别?
主键强调唯一标识, 外键强调数据关联和引用完整性.
2. 高频面试问题
先别先死磕 MVCC、间隙锁、两阶段提交这种深题。先把索引这条线背熟,因为面试官最容易从“索引是什么”一路追到“B+树、回表、覆盖索引、最左前缀、索引失效、Explain”。先把这条线答顺了,比零散背 25 题有用得。
A. SQL 与基础设计
1. MySQL查询语句执行顺序
核心就是一句话: 先找表, 然后过滤,然后分组,然后过滤组, 再选字段, 最后排序分页
MySQL的执行顺序不是按照书写顺序执行的, 逻辑执行顺序一般是先From确定数据来源, 再where进行过滤, 然后group by进行分组, having过滤分组,接着select选择字段, 最后order by进行排序, limit限制返回条数
举一个例子:
SELECT department, COUNT(*) AS cnt
FROM employee
WHERE age > 25
GROUP BY department
HAVING cnt > 3
ORDER BY cnt DESC
LIMIT 10;
从emploee表取数据
先筛选
age>25再按
department进行分组再保留人数大于三的分组
查询部门和人数
按人数倒序进行排序
只返回前10条
2. WHERE 和 HAVING 区别
核心一句话:
WHERE是分组前过滤行,HAVING是分组后过滤组。
面试回答:
WHERE用于GROUP BY之前,过滤的是原始数据行,一般不能直接使用聚合函数;HAVING用于GROUP BY之后,过滤的是分组后的聚合结果,可以使用COUNT、SUM、AVG等聚合函数。
例子:
SELECT department, COUNT(*) AS cnt
FROM employee
WHERE age > 25
GROUP BY department
HAVING COUNT(*) > 3;
这里:
WHERE age > 25:先过滤年龄大于 25 的员工HAVING COUNT(*) > 3:再过滤人数大于 3 的部门
3. INNER JOIN、LEFT JOIN、RIGHT JOIN 区别
核心一句话:
INNER JOIN只保留两边都匹配的数据;LEFT JOIN保留左表全部数据;RIGHT JOIN保留右表全部数据。
面试回答:
INNER JOIN表示内连接,只返回两张表中能匹配上的数据;LEFT JOIN表示左连接,会返回左表的全部数据,右表匹配不到的字段用NULL填充;RIGHT JOIN表示右连接,会返回右表的全部数据,左表匹配不到的字段用NULL填充。
4. COUNT(*)、COUNT(1)、COUNT(列名) 区别
核心一句话:
COUNT(*)和COUNT(1)都统计行数,COUNT(列名)不统计NULL。
面试回答:
COUNT(*)统计表中满足条件的总行数,包括值为NULL的行;COUNT(1)本质也是统计行数,和COUNT(*)差别很小;COUNT(列名)只统计该列不为NULL的行数。所以如果只是统计总行数,通常推荐使用COUNT(*),语义最清晰。
5. DELETE、TRUNCATE、DROP 区别
核心一句话:
DELETE删除数据,TRUNCATE清空表,DROP删除表结构。
面试回答:
DELETE是 DML 操作,可以带WHERE条件删除部分数据,会记录日志,可以回滚;TRUNCATE是清空整张表,不能加WHERE,速度通常更快,会重置自增 ID;DROP是直接删除整张表,包括表结构和数据。
6. CHAR 和 VARCHAR 区别
核心一句话:
CHAR定长,VARCHAR变长。
面试回答:
CHAR是定长字符串,比如CHAR(10),即使只存 3 个字符,也会占用固定长度;VARCHAR是变长字符串,只占用实际内容长度加额外长度记录。CHAR适合长度固定的字段,比如性别、状态码;VARCHAR适合长度不固定的字段,比如用户名、标题、邮箱。
7. 金额字段为什么用 DECIMAL 或 BIGINT
核心一句话:
金额不能用
FLOAT/DOUBLE,因为它们有精度误差。
面试回答:
金额字段通常不能用
FLOAT或DOUBLE,因为它们是浮点数,存在精度误差。一般有两种做法:一种是用DECIMAL保存精确小数,比如DECIMAL(10,2);另一种是用BIGINT以“分”为单位存储整数,比如 100.25 元存成 10025 分。这样可以避免金额计算误差。
B. 索引
8. MySQL 索引是什么?
核心一句话:
索引是帮助 MySQL 快速查找数据的数据结构,类似书的目录。
面试回答:
MySQL 索引是一种用于提高查询效率的数据结构,常见实现是 B+ 树。没有索引时,MySQL 可能需要全表扫描;有索引后,可以通过索引快速定位数据,减少扫描行数。但索引也会占用额外空间,并且会降低插入、更新、删除的性能,所以不是越多越好。
9. 为什么 InnoDB 使用 B+ 树?
核心一句话:
B+ 树层高低、范围查询强、磁盘 IO 少,适合数据库索引。
面试回答:
InnoDB 使用 B+ 树,主要是因为 B+ 树非常适合磁盘存储场景。它的非叶子节点只存索引 key,不存完整数据,所以一个节点可以存更多 key,树的高度更低,查询时磁盘 IO 更少。同时 B+ 树的叶子节点之间通过链表连接,非常适合范围查询和排序查询。
可以补一句:
相比二叉树,B+ 树更矮;相比 Hash 索引,B+ 树支持范围查询和排序。
10. 聚簇索引和非聚簇索引区别
核心一句话:
聚簇索引的叶子节点存整行数据,非聚簇索引的叶子节点存主键值。
面试回答:
在 InnoDB 中,主键索引就是聚簇索引,叶子节点存储的是整行数据;普通索引是非聚簇索引,叶子节点存储的是索引字段值和对应的主键值。如果通过普通索引查到了主键,但查询字段不在索引中,就需要再根据主键去聚簇索引中查整行数据,这个过程叫回表。
11. 什么是回表?
核心一句话:
通过普通索引查到主键后,再去主键索引查整行数据,这个过程叫回表。
面试回答:
回表通常发生在使用普通索引查询时。因为普通索引的叶子节点只保存索引字段和主键值,如果查询的字段不在普通索引里,MySQL 需要先通过普通索引找到主键,再根据主键去聚簇索引中查询完整数据,这个过程就叫回表。
例子:
SELECT name, age FROM user WHERE name = '张三';
如果只有 name 索引,但 age 不在索引里,就可能需要回表。
12. 什么是覆盖索引?
核心一句话:
查询需要的字段都在索引里,不需要回表,就是覆盖索引。
面试回答:
覆盖索引是指 SQL 查询需要的字段都可以从索引中直接获取,不需要再回到聚簇索引查询整行数据。覆盖索引可以减少回表次数,提高查询性能。
例子:
CREATE INDEX idx_name_age ON user(name, age);
SELECT name, age FROM user WHERE name = '张三';
这里 name 和 age 都在联合索引 idx_name_age 中,就可能走覆盖索引。
13. 什么是最左前缀原则?
核心一句话:
联合索引从最左边字段开始连续匹配,不能跳过中间字段。
面试回答:
最左前缀原则是指使用联合索引时,查询条件必须从索引最左边的字段开始连续匹配。如果跳过了最左字段,或者中间字段断开,后面的字段就可能无法充分利用索引。
例子:
CREATE INDEX idx_a_b_c ON t(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;
WHERE b = 2 AND c = 3;
14. 哪些情况会导致索引失效?
核心一句话:
对索引列做函数、计算、隐式转换、左模糊匹配,或者不符合最左前缀,都可能导致索引失效。
面试回答:
常见索引失效场景包括:对索引列使用函数或表达式计算;字符串字段没有加引号导致隐式类型转换;使用
LIKE '%xxx'这种左模糊匹配;联合索引不符合最左前缀原则;使用OR时部分条件没有索引;以及查询条件选择性太差时,优化器可能选择全表扫描。
常见例子:
WHERE YEAR(create_time) = 2024;
WHERE age + 1 = 20;
WHERE phone = 123456;
WHERE name LIKE '%三';
15. 慢 SQL 怎么排查?
核心一句话:
先定位慢 SQL,再用 Explain 分析执行计划,重点看是否走索引、扫描行数、是否回表、是否排序临时表。
面试回答:
排查慢 SQL 一般先通过慢查询日志定位具体 SQL,然后用
EXPLAIN查看执行计划,重点看type、key、rows、Extra等字段,判断是否走了索引、扫描行数是否过多、是否出现Using filesort或Using temporary。然后根据情况优化索引、改写 SQL、减少返回字段、避免回表,必要时做分页优化、分库分表或缓存。
16. Explain 主要看哪些字段?
核心一句话:
主要看
type、key、rows、Extra。
面试回答:
EXPLAIN主要用来查看 SQL 的执行计划。我一般重点看几个字段:type表示访问类型,判断是全表扫描还是索引查询;key表示实际使用的索引;rows表示预估扫描行数;Extra表示额外信息,比如是否出现Using filesort、Using temporary、Using index。其中type越接近const、ref、range越好,ALL通常表示全表扫描,需要重点关注。
C. 事务与 MVCC
17. 什么是事务 ACID?
核心一句话:
ACID 是事务的四个特性:原子性、一致性、隔离性、持久性。
面试回答:
事务的 ACID 包括四个特性:原子性表示事务中的操作要么全部成功,要么全部失败;一致性表示事务执行前后数据要满足约束和业务规则;隔离性表示多个事务并发执行时互不干扰;持久性表示事务一旦提交,数据就会被持久保存,即使数据库宕机也不应该丢失。
18. MySQL 有哪些隔离级别?
核心一句话:
读未提交、读已提交、可重复读、串行化。MySQL 默认是可重复读。
面试回答:
MySQL 事务隔离级别有四种:读未提交、读已提交、可重复读、串行化。读未提交可能出现脏读;读已提交解决脏读,但可能出现不可重复读;可重复读解决不可重复读,是 MySQL InnoDB 默认隔离级别;串行化隔离级别最高,但并发性能最差。
19. 脏读、不可重复读、幻读分别是什么?
核心一句话:
脏读读到未提交数据;不可重复读是同一行前后读到不同值;幻读是前后查询到的记录数量不一致。
面试回答:
脏读是一个事务读到了另一个事务还没有提交的数据,如果对方回滚,就读到了无效数据。不可重复读是同一个事务中,两次读取同一行数据,结果不一致,通常是因为其他事务修改并提交了这行数据。幻读是同一个事务中,两次按照相同条件查询,结果集数量不一致,通常是其他事务插入或删除了符合条件的数据。
20. MVCC 是怎么实现的?
核心一句话:
MVCC 通过隐藏字段、undo log 和 ReadView 实现快照读。
面试回答:
MVCC 是多版本并发控制,主要用来提高并发读写性能。InnoDB 中每行数据有隐藏字段,比如事务 ID 和回滚指针;更新数据时,会通过 undo log 保留旧版本;事务查询时会生成 ReadView,根据 ReadView 判断当前事务能看到哪个版本的数据。这样普通查询可以读取快照版本,不需要加锁,从而实现读写并发。
21. undo log 有什么作用?
核心一句话:
undo log用于事务回滚和 MVCC 版本链。
面试回答:
undo log主要有两个作用:第一是用于事务回滚,当事务执行失败或主动回滚时,可以根据 undo log 恢复到修改前的数据;第二是用于 MVCC,当其他事务需要读取旧版本数据时,可以通过 undo log 形成的版本链找到可见的数据版本。
22. 当前读和快照读区别?
核心一句话:
快照读读历史版本,不加锁;当前读读最新版本,通常要加锁。
面试回答:
快照读是读取数据的历史版本,普通
SELECT一般就是快照读,依赖 MVCC 实现,不会加锁。当前读读取的是数据的最新版本,并且通常会加锁,比如SELECT ... FOR UPDATE、UPDATE、DELETE、INSERT都属于当前读。当前读需要保证读到的是最新数据,并防止其他事务并发修改。
D. 锁与日志
23. MySQL 有哪些锁?行锁、表锁、间隙锁、临键锁分别是什么?
核心一句话:
表锁锁整张表,行锁锁具体行,间隙锁锁范围间隙,临键锁等于行锁 + 间隙锁。
面试回答:
MySQL 常见锁包括表锁、行锁、间隙锁、临键锁等。表锁是锁整张表,粒度大,并发性能低;行锁是锁具体的数据行,粒度小,并发性能高,是 InnoDB 常用锁;间隙锁锁的是索引记录之间的范围,用来防止其他事务在这个范围插入数据;临键锁是行锁和间隙锁的组合,锁住某条记录以及它前面的间隙,主要用于解决幻读问题。
24. redo log、undo log、binlog 分别有什么作用?
核心一句话:
redo log保证持久性,undo log保证回滚和 MVCC,binlog用于主从复制和数据恢复。
面试回答:
redo log是 InnoDB 的重做日志,主要保证事务的持久性,数据库宕机后可以通过 redo log 恢复已提交事务的数据;undo log是回滚日志,用于事务回滚和 MVCC 版本链;binlog是 MySQL Server 层的二进制日志,记录数据库变更操作,主要用于主从复制和基于时间点的数据恢复。
25. 为什么 redo log 和 binlog 要两阶段提交?
核心一句话:
为了保证
redo log和binlog的一致性,避免主库和从库数据不一致。
面试回答:
redo log是 InnoDB 层的日志,binlog是 MySQL Server 层的日志。一个事务提交时,两份日志都要写。如果只写成功其中一个,可能会导致数据库崩溃恢复后的数据和 binlog 中记录的数据不一致,从而影响主从复制和数据恢复。所以 MySQL 使用两阶段提交:先把 redo log 写成 prepare 状态,再写 binlog,最后提交 redo log。这样即使中间宕机,也可以根据两份日志的状态判断事务是否应该提交,保证一致性。