MySQL面试30问:三天吃透索引、事务、锁与SQL优化核心考点
2026/9/8 7:36:19 网站建设 项目流程

后端 MySQL 面试夺命 30 问,三天吃透核心考点,你的后端面试就稳了

后端面试,MySQL 从来不是“可选项”,而是“必选项”。很多人背了一堆八股文,结果面试官换一个问法就卡壳——比如同样是问索引,他不问“B+树和B树的区别”,而是问“为什么InnoDB的二级索引要存主键而不是行地址”。这背后考的不是记忆力,而是你有没有真正理解MySQL的底层实现逻辑。

我见过太多候选人,项目写了三四年,会用框架、会写CRUD,但对索引失效、事务隔离、死锁排查完全说不清楚。说白了,后端开发越往上走,MySQL的掌握深度越直接决定你的技术上限。数据库一旦成为瓶颈,Java代码写得再花哨都没用。

本文整理了30个后端高频MySQL面试题,按“架构原理 → 索引 → 事务与锁 → SQL优化 → 存储与运维”五个维度拆开,每题都给出核心答案、底层原理和面试官真正想听的要点。不搞标题党的假大空,只讲面试场上真正会被追问的东西。每天吃透10题,三天过完一遍,配合实践去验证,你的后端面试就稳了。

1. 面试官第一题必问:一条SQL在MySQL内部是怎么执行的

第1题:一条普通查询SQL,在MySQL中完整的执行流程是什么?

这是判断候选人是“会用MySQL”还是“懂MySQL”的分水岭。完整链路如下:

  1. 连接器:客户端通过TCP握手建立连接,连接器负责身份认证、权限校验、连接管理。这里要记住连接是“懒加载”资源,长时间空闲超过wait_timeout(默认8小时)会被断开,所以生产环境必须配连接池,并设置合理的maxLifetimeminEvictableIdleTime
  2. 查询缓存:MySQL 8.0已经彻底移除了查询缓存功能,因为全局缓存失效太频繁,写入操作会直接清空整张表的缓存,反而成为性能瓶颈。如果面试官问这项功能,直接说8.0之前有但推荐关闭即可。
  3. 分析器:对SQL做词法分析和语法分析,生成语法树。这里要理解,如果表名或字段名写错,报错发生在这一步,SQL根本没有真正执行。
  4. 优化器:决定SQL执行的“最优路径”,核心工作是决定使用哪个索引、多表连接的顺序、子查询怎么改写。优化器会基于统计信息做估算,这也是为什么ANALYZE TABLE能帮助优化器做更准确判断的原因。
  5. 执行器:打开表获取行数据,先判断是否命中了权限范围,然后调用存储引擎接口逐行读取、判断WHERE条件、返回结果集。
  6. 存储引擎层:真正落盘读写数据,InnoDB负责缓存、事务、锁、日志等底层机制。

面试加分回答:在整个流程中,优化器和执行器是最容易出问题的两层。优化器选错索引是开发中最头痛的事情,而执行器的rows_examined可以反映扫描行数,定位SQL是否“实际干的活”比预期多。

2. MySQL架构与核心存储引擎

2.1 第2题:InnoDB和MyISAM的底层差异是什么

这个对比题几乎每个面试都会出现。核心差异有几层:

对比维度InnoDBMyISAM
事务支持支持ACID事务不支持
锁粒度行级锁表级锁
崩溃恢复redo log + binlog两阶段提交
外键支持不支持
全文索引8.0前不支持支持
表数据存储聚簇索引组织堆表

真正引发面试官兴趣的回答:InnoDB之所以支持事务和行锁,核心区别在于它将数据按主键聚簇存储,二级索引全部引用主键;而MyISAM的索引文件和数据文件分离,索引叶子节点存的是行地址。崩溃恢复能力的差异在于redo log的存在,MyISAM写入只能依赖操作系统缓存,一旦宕机就面临数据丢失风险。

生产环境中,如果不是极端的只读场景,不用犹豫直接选InnoDB。MySQL 8.0的默认引擎就是InnoDB。

2.2 第3题:InnoDB的Buffer Pool为什么能提高性能

Buffer Pool是InnoDB在内存中维护的一块缓存区域,缓存了最近访问的数据页和索引页。查询数据时,先从Buffer Pool中找,找到直接返回,没找到再去磁盘读,并把读到的页放入Buffer Pool。

关键参数:

# my.cnf 示例 innodb_buffer_pool_size=8G innodb_buffer_pool_instances=8

实践经验:innodb_buffer_pool_size建议设置为物理内存的60%~70%,但不能超过总内存导致swap。实例数默认8个,目的是降低并发访问同一块缓冲池的锁竞争。监控命中率可以用:

SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';

Innodb_buffer_pool_read_requests是总请求数,Innodb_buffer_pool_reads是磁盘读取次数。命中率 = 1 - reads/read_requests,如果命中率低于95%,说明缓存太小或SQL扫描了太多数据。

2.3 第4题:redo log、undo log、binlog 三者的区别是什么

这是高频中的高频,而且面试官往往喜欢连环追问。简洁清晰的答案如下:

redo log:InnoDB引擎层的物理日志,记录的是“某个数据页做了什么修改”,作用是崩溃恢复。因为WAL(Write-Ahead Logging)机制,事务提交前先写日志,再写磁盘,崩溃后通过重放redo log恢复未落盘的数据。默认innodb_flush_log_at_trx_commit=1,每次事务提交都刷盘,速度慢但最安全。

undo log:记录修改前的数据状态,用于事务回滚和MVCC快照读。每一条INSERT会生成DELETE undo,每一条UPDATE会生成UPDATE undo。事务回滚时,根据undo log逆向执行就能还原数据。

binlog:MySQL Server层的逻辑日志,记录的是SQL语句或行变更,主要用于主从复制和数据恢复。binlog有STATEMENT、ROW、MIXED三种格式,生产环境建议用ROW格式。

三者对比速记:redo log保证事务的持久性,undo log保证原子性和多版本控制,binlog负责复制和恢复。InnoDB通过两阶段提交让redo log和binlog保持一致,避免崩溃后主从数据不一致。

3. 索引的本质与设计原则

3.1 第5题:为什么InnoDB一定要用B+树而不是B树、红黑树、哈希表

这个问题值得从数据结构演进角度回答。先说结论:

  • 哈希表:等值查询O(1)最优,但无法支持范围查询和排序。
  • 红黑树:二叉平衡树,树高约log₂N,10亿数据大概30层,每层一次磁盘IO,太多了。
  • B树:多路平衡搜索树,所有节点都存储数据,非叶子节点也占空间,同样容量下能存储的索引条目更少,树更高。
  • B+树:只有叶子节点存数据,非叶子节点只存索引键和指针。一个16KB页可以存约1170个索引键和1170个指针,3层B+树大约能存2000多万条记录,意味着查询任何一条记录最多3次磁盘IO,叶子节点之间用链表连接,天然支持范围扫描和排序。

一层本质理解:B+树把“树的高度”和“磁盘IO次数”绑定。数据库索引的决策变量从来不是CPU比较次数,而是磁盘寻道次数。B+树的非叶子节点越小,单页能装下的“路数”越多,树越矮,IO越少。

3.2 第6题:聚簇索引和二级索引有什么区别?什么是回表

InnoDB默认按主键构建的索引就是聚簇索引,叶子节点存储的是整行数据。二级索引(普通索引、联合索引)叶子节点存储的是主键值,不是行数据。

查询二级索引时,先找到主键值,再用主键值去聚簇索引中查整行数据,这个过程叫做“回表”。

-- 假设 id 为主键,name 上有普通索引 SELECT * FROM user WHERE name = 'zhangsan'; -- 执行过程: -- 1. 通过 name 二级索引找到 id = 5 -- 2. 再通过 id = 5 回表查聚簇索引,取出整行 SELECT id FROM user WHERE name = 'zhangsan'; -- 这条语句只查id,二级索引中直接有,无需回表,也叫覆盖索引

面试加分点:回表不是必然的。如果查询的所有列都包含在索引中,就无需回表,这叫覆盖索引优化。这也是为什么很多查询要把SELECT的列收窄,而不是无脑SELECT *。

3.3 第7题:联合索引的最左前缀原则是什么

联合索引 (a, b, c) 到底怎么走索引?规则是:从最左列开始匹配,遇到范围查询(>、<、BETWEEN)就会停止匹配后续列,遇到非等值判断也停止。

-- 索引 (a, b, c) WHERE a = 1 AND b = 2 AND c = 3; -- 完整命中索引 WHERE a = 1 AND b > 2 AND c = 3; -- 命中到b,c无法走索引 WHERE b = 2 AND c = 3; -- 完全无法走索引 WHERE a = 1 AND c = 3; -- a能走索引,c不能

很多人背了这个概念但没理解背后的物理原因。B+树的索引是先按第一列排序,才按第二列排序。所以必须遵循最左列的顺序才能利用索引的有序性。

实际设计建议:把高区分度的列放左边、等值查询的列放左边、范围查询的列放右边,然后根据真实业务SQL频率调整顺序。

3.4 第8题:为什么用select *会导致性能问题

这不只是面试题,更是日常代码review中每个后端都要盯的习惯问题。总结起来有四个坑:

  1. 返回不需要的字段,增加网络IO和内存消耗。
  2. 无法使用覆盖索引,导致二级索引全部回表。
  3. 如果表结构后续变更(增加大字段),select *会让所有调用方的返回结果集变大,不可控。
  4. 无法利用MySQL的索引条件下推ICP(Index Condition Pushdown)做合理优化。

真实优化案例中,把SELECT *改成只查必要的字段,配合覆盖索引,查询效率翻倍是常事。

3.5 第9题:索引失效的常见场景有哪些

这是面试笔试高频实操题,也是工作中排查慢SQL最常遇到的情况。

场景原因解决方案
WHERE name LIKE '%abc'前模糊无法利用B+树有序性改为后模糊,或使用全文索引
WHERE DATE(create_time) = '2024-05-01'对索引列使用函数改为create_time >= '2024-05-01' AND create_time < '2024-05-02'
WHERE id + 1 = 5对索引列做运算改成id = 4
WHERE status != 1不等于通常无法走索引业务上考虑是否用IN拆分
隐式类型转换字符串列和数字比较保证类型一致
OR连接非索引列合并结果集无法用索引拆分成UNION或加索引

核心判断原则:破坏索引列的有序性,就会导致索引失效。函数、运算、前模糊本质上都破坏了有序性。

3.6 第10题:什么时候需要强制指定索引或创建新索引

面试考设计能力时会问。建立一个判断清单:

  1. 查询频率高、数据量大(超过百万级)的表,WHERE和ORDER BY涉及的列必须考虑索引。
  2. 区分度太低不建索引,比如性别字段,区分度不足50%,索引扫描范围依然很大,甚至不如全表扫描。
  3. 更新频繁的列不建议加过多索引,因为每次UPDATE都要同步维护索引B+树。
  4. 如果优化器选错索引,可以先ANALYZE TABLE更新统计信息;仍然不对,才用FORCE INDEXUSE INDEX,但要注意这会让SQL依赖特定索引名,不优雅。

4. 事务、隔离级别与MVCC

4.1 第11题:事务的ACID特性分别由什么机制保证

直接回答对应机制:

  • 原子性:undo log。事务执行中出错,通过undo log回滚到事务开始前的状态。
  • 一致性:应用层加数据库约束共同保证。数据库层面通过外键、CHECK约束、触发器等,但真正的一致性是业务逻辑自己控制的。
  • 隔离性:锁 + MVCC。
  • 持久性:redo log + binlog双写。

深入的关键点:MySQL默认autocommit=1,每条DML语句都是自动提交的。如果你在代码中执行多条DML语句而不显式开启事务,每一句都是独立事务,中途失败后前面成功的语句不会被回滚。这是导致数据不一致的常见隐性原因。

4.2 第12题:MySQL有哪几种隔离级别,默认是什么

SQL标准定义了四种隔离级别:

  1. 读未提交(READ UNCOMMITTED):能读到其他事务未提交的数据,存在脏读。
  2. 读已提交(READ COMMITTED):只能读到已提交的数据,解决脏读,但存在不可重复读。
  3. 可重复读(REPEATABLE READ):同一个事务内多次读取结果一致,解决不可重复读,MySQL默认级别。
  4. 串行化(SERIALIZABLE):所有事务串行执行,最安全但并发极低。

MySQL为什么默认用可重复读而不是读已提交?历史原因是binlog在STATEMENT格式下,读已提交会产生主从数据不一致的问题。现代MySQL 8.0建议生产环境按需调整为读已提交,能降低锁和其他问题出现的概率。

脏读:读到另一个事务未提交的数据。不可重复读:同一事务内,同一条SELECT两次结果不同,因为别的事务提交了UPDATE。幻读:同一事务内,范围查询两次结果的行数不同,因为别的事务提交了INSERT。

4.3 第13题:MVCC是什么?为什么能解决可重复读

MVCC(Multi-Version Concurrency Control),多版本并发控制。核心思想:数据行不只有一个版本,每个版本通过隐藏列记录事务ID,读操作通过游标机制读到“事务开始那一刻”的快照。

在InnoDB中,每行数据都有三个隐藏列:

  • DB_TRX_ID:最近修改该行的事务ID。
  • DB_ROLL_PTR:指向undo log中该行上一个版本的指针。
  • DB_ROW_ID:隐藏主键(没有显式主键时)。

读操作生成一个ReadView,包含活跃事务列表和最小最大事务ID。判断行版本是否可见的规则是:

  • 行的DB_TRX_ID小于min_trx_id,说明在事务开始前已提交,可见。
  • 大于max_trx_id,说明在事务开始后启动,不可见。
  • 在活跃列表中,说明未提交,不可见。

可重复读的“魔力”在于:事务开始第一次读时生成ReadView,整个事务期间复用同一个ReadView,后续读到的都是同一个快照,自然避免了不可重复读和幻读。

4.4 第14题:MySQL的锁有哪些分类?快照读和当前读是什么

锁的分类:

  • 按粒度分:表锁、行锁、间隙锁、临键锁。
  • 按模式分:共享锁(S)、排他锁(X)。
  • 按思想分:悲观锁、乐观锁。

注意区分两种读:

快照读:普通SELECT,通过MVCC读取历史版本,不加锁,性能和并发最好。

当前读SELECT ... FOR UPDATEUPDATEDELETE,读取最新数据并加锁。UPDATE操作本质是先当前读找到要改的行,再写回新版本。

一个经典案例:

-- 悲观锁 SELECT * FROM order WHERE id = 1 FOR UPDATE; -- 更新业务逻辑
// 乐观锁 // 表加 version 字段,更新时SQL UPDATE order SET amount = 100, version = version + 1 WHERE id = 1 AND version = 5; // 影响行数为0说明版本已变化,需要重试

面试加分点:乐观锁不是数据库功能,而是业务设计模式,靠版本号或时间戳实现;悲观锁才是MySQL行锁机制的核心。

4.5 第15题:间隙锁(Gap Lock)和临键锁(Next-Key Lock)是什么

在可重复读隔离级别下,InnoDB为了防止幻读,引入了间隙锁。

间隙锁:锁住索引记录之间的“空隙”,使其他事务无法在这个区间插入新数据。

临键锁:记录锁 + 间隙锁的组合,锁住当前记录和它前面的间隙,左开右闭区间。

-- 假设 id 有索引,数据 id 为:1,5,10 SELECT * FROM user WHERE id BETWEEN 5 AND 10 FOR UPDATE; -- 会锁住 (5,10] 间隙和 id=10 的记录 -- 阻止其他事务插入 id=6,7,8,9 的数据

真正关键的是:间隙锁只有在可重复读级别才生效。如果业务允许,把隔离级别降为读已提交,就能避免大部分死锁和锁等待问题。

4.6 第16题:死锁是怎么发生的,如何排查和避免

死锁的经典场景是两个事务持有对方需要的锁,互相等待。

事务A: update user set name='a' where id=1; -- 持有id=1行锁 事务A: update user set name='b' where id=2; -- 等待id=2行锁 事务B: update user set name='c' where id=2; -- 持有id=2行锁 事务B: update user set name='d' where id=1; -- 等待id=1行锁 -- 死锁形成

排查死锁命令:

SHOW ENGINE INNODB STATUS\G

重点看LATEST DETECTED DEADLOCK部分,里面会显示两个事务执行的SQL和持有的锁。

避免死锁的实践原则:

  1. 多个事务按相同顺序操作资源。
  2. 一次SQL尽量用批量更新,减少锁持有时间。
  3. 事务尽量短小,控制在一个事务内的SQL数量。
  4. 对热点行更新使用乐观锁重试机制。

5. SQL优化与慢查询排查

5.1 第17题:EXPLAIN怎么用,每个字段代表什么意思

EXPLAIN 是后端排查慢SQL的第一工具,必须能完整说出每个字段含义。

EXPLAIN SELECT u.id, u.name, o.order_no FROM user u JOIN order o ON u.id = o.user_id WHERE u.age > 18;

重点字段解读:

字段含义面试关注点
type访问类型all(全表扫描)、index(全索引扫描)、range、ref、eq_ref、const
possible_keys可能使用的索引与最终结果对比看出优化器选择
key实际使用的索引为空说明没用到
ref与索引匹配的列或常量const 说明是常量比较
rows预估扫描行数越小越好,但只是估算值
filtered过滤比例百分比越大越好
Extra额外信息重点看Using filesort、Using temporary、Using index

实战中判断语句优化是否有效,对比优化前后的rowsExtra即可。看到Using filesort意味着ORDER BY没有走索引,看到Using temporary意味着GROUP BY或去重用了临时表,这两个都是性能红灯。

5.2 第18题:慢查询日志怎么配置和开启

生产环境需要定期分析慢SQL。

# my.cnf slow_query_log = ON slow_query_log_file = /var/log/mysql/slow.log long_query_time = 1 log_queries_not_using_indexes = ON

long_query_time=1表示超过1秒的SQL都记录。日常分析常用mysqldumpslow工具:

mysqldumpslow -s at -t 10 /var/log/mysql/slow.log

-s at按平均查询时间排序,-t 10取前10条。生产环境建议把long_query_time设为0.5秒甚至更低,因为对于一个有索引的表,正常的单行查询应当稳定在10ms以内,超过1秒一定有问题。

5.3 第19题:为什么ORDER BY会造成性能瓶颈

ORDER BY如果走不了索引,MySQL需要把结果集先放入排序缓冲区,再执行排序操作。如果结果集超过sort_buffer_size,就会使用磁盘临时文件排序,性能断崖式下降。

-- 索引 (age, create_time),能走索引排序 SELECT * FROM user WHERE age = 20 ORDER BY create_time DESC; -- 如果ORDER BY的列和索引顺序不一致,或中间夹了范围查询 SELECT * FROM user WHERE age > 20 ORDER BY create_time DESC; -- 这里 age 使用了范围查询,create_time 无法利用索引排序,导致filesort

面试加分点:排序多列时,如果既有升序又有降序,MySQL可能无法利用索引,因为索引默认是升序存储。MySQL 8.0以后支持降序索引来解决这个问题。

5.4 第20题:分页查询在数据量很大的情况下为什么变慢

经典问题:LIMIT 100000, 20为什么慢?

因为MySQL会扫描前100000行然后丢弃,只返回最后20行。数据量越大,偏移量越大,性能越差。

-- 低效写法 SELECT * FROM order_log ORDER BY id DESC LIMIT 100000, 20; -- 高效写法:先走覆盖索引快速定位,再回表 SELECT * FROM order_log WHERE id < 100001 ORDER BY id DESC LIMIT 20;

第二种方式利用主键索引快速跳过偏移,避免全量扫描和排序。要注意的是,这种方式只适用于按主键或唯一索引排序的场景,业务排序规则复杂时需要改造成“带游标”的方式。

5.5 第21题:大表JOIN为什么慢,如何优化

后端最常踩的坑就是不加思考地JOIN三张以上的大表。JOIN慢的本质是嵌套循环,驱动表的每一行都要去被驱动表找匹配关系。

优化思路:

  1. 小表驱动大表:调整SQL让结果集小的放左边。
  2. JOIN字段必须有索引,否则对每行都做全表扫描。
  3. 减少被驱动表的扫描行数,把能过滤的条件尽量提前。
  4. 必要时拆成多次查询,在应用层做数据组装。
  5. 大偏移量的分页JOIN可以先用子查询查出主键分页,再JOIN原表。

5.6 第22题:count(*) 、count(1)、count(id) 到底有什么区别

很多人以为这三者性能差异巨大,其实在InnoDB中,它们都会扫描全表或全索引,结果没有本质区别。

关键区别在于“是否统计NULL值”:

  • count(*)统计行数,不检查字段内容,性能最好。
  • count(1)统计行数,等价于count(*)。
  • count(id)统计id不为NULL的行数,如果主键不允许NULL,和上面等价。
  • count(字段)只统计该字段不为NULL的行数。

count(*)无法走索引吗?实际上MySQL 8.0还是会扫描索引来统计,因为索引比聚簇索引小。如果表有二级索引,会优先选择最小的二级索引扫描。大数据量实时统计一定要用汇总表或Redis缓存计数。

5.7 第23题:为什么乐观锁更新性能比悲观锁好

乐观锁不持有数据库锁,而是在更新时校验版本号。它把并发控制的成本转移到了业务层,避免了长时间的行锁持有,所以读多写少场景下性能更好。

@Transactional public boolean updateOrder(Order order) { int count = orderMapper.updateByIdAndVersion(order); // SQL: UPDATE order SET amount = #{amount}, version = version + 1 // WHERE id = #{id} AND version = #{version} return count > 0; }

但注意,乐观锁不适合写并发非常高的场景,因为大量更新会因为版本不一致而失败,重试的成本可能比锁等待还高。热点账户余额扣减这种场景,用悲观锁或Redis原子操作更合理。

6. 存储过程、视图与触发器

6.1 第24题:存储过程还有必要用吗

现在大厂面试很少让你写复杂存储过程,但会调查你对它利弊的理解。

存储过程的优点:

  • 减少客户端和服务器之间的网络传输,一次调用执行多条复杂SQL。
  • 数据库逻辑复用,适合强事务、多步骤的业务。
  • 可以在数据库层面做权限控制,对应用层隐藏表结构。

存储过程的缺点:

  • 开发和调试效率低,没有IDE好用的断点。
  • 难以做版本控制,和代码一起管理不方便。
  • 对数据库实例的CPU和内存消耗更大,难以水平扩展。
  • 业务逻辑放在数据库,导致拆分微服务时困难。

现实结论:新项目基本不推荐用存储过程,但老系统里仍然常见,面试时表明“能用代码解决就不放在数据库层”会更符合现代后端工程理念。

6.2 第25题:触发器有什么坑

触发器是自动执行存储在数据库中的PL/SQL块,在INSERT、UPDATE、DELETE前后触发。

典型的坑有两个:

  1. 隐性逻辑:业务代码中看不出来数据被额外修改了,排障极难。
  2. 多层触发器:一个触发器里又更新了另一张表,又触发第三个触发器,性能和死锁问题都会出现。

生产环境更推荐用“事件发布 + 应用层监听”的方式替代触发器。比如订单状态变更后,在Java代码中发MQ消息给下游系统,而不是用触发器同步更新冗余表。

7. 主从复制、分库分表与高可用

7.1 第26题:MySQL主从复制的原理是什么,延迟怎么解决

主从复制是MySQL高可用的基石,原理不复杂:

  1. 主库提交事务时,把变更写入binlog。
  2. 从库的IO线程连接主库,请求binlog,并写入自己的中继日志(relay log)。
  3. 从库的SQL线程读取relay log,串行重放变更,应用到自己的数据上。

面试必问的一个点:主从延迟怎么解决。原因通常是:

  • 从库硬件性能不如主库。
  • 主库写入并发高,从库SQL线程只能单线程重放。
  • 大事务造成延迟累积。

解决方案:

# 从库配置 slave_parallel_workers = 4 slave_parallel_type = LOGICAL_CLOCK

MySQL 8.0支持基于组提交的并行复制,可以让从库多个线程并行应用不同事务。业务侧则要注意:刚写入的数据立刻去读从库,可能读到旧数据。这种场景强制走主库,或用读写分离中间件设置主从同步延迟阈值。

7.2 第27题:读写分离是怎么实现的,有哪些坑

读写分离通常由中间层实现,常见方案有:

  • MySQL主从复制 + 应用层路由(自己封装数据源)。
  • ShardingSphere-JDBC:客户端原生支持读写分离。
  • ProxySQL / MyCat:在数据库前加代理,SQL自动路由。

坑点在于:

  1. 主从延迟导致读不到刚写入的数据,需要提供主库路由开关。
  2. 事务内的读操作必须强制走主库,否则同一事务内数据自相矛盾。
  3. 对一致性要求高的接口,不要使用读写分离。
  4. 连接池要区分主库和从库的容量,防止从库被读请求打垮。

7.3 第28题:分库分表到底该怎么做,什么时候做

切记:分库分表是最后手段,不是最优手段。合理顺序应该是:SQL优化 → 加索引 → 加缓存 → 读写分离 → 分库分表。

什么时候必须分:

  • 单表超过千万级,且查询性能无法通过索引解决。
  • 写入并发极高,单库的IO和连接数成为瓶颈。
  • 数据库实例磁盘容量不够,按天分表归档历史数据。

分库分表策略:

策略方式优缺点
水平分表按ID哈希或范围路由到不同表减轻单表压力,但聚合查询难
水平分库多实例部署,同一逻辑表分散并发能力提升,跨库JOIN难
垂直分库按业务模块拆分结构清晰,单库权限隔离,但分布式事务复杂
垂直分表大字段拆成独立表减少IO,查询变快,但SQL要改

分库分表后必须解决的两个问题:全局主键(雪花ID)、跨分片查询和聚合。雪花ID要配置机器ID和数据中心ID,避免全局冲突。跨分片查询尽量在业务上避免,或通过汇总表异步聚合。

7.4 第29题:如何做MySQL数据备份和恢复

面试考的是你有没有生产意识,而不是会不会敲命令。

常用工具是mysqldump

# 全量备份 mysqldump -u root -p --single-transaction --master-data=2 -A > backup.sql

参数解释:--single-transaction对InnoDB表做一致性快照,不会锁表;--master-data=2在备份文件中记录binlog位置,方便增量恢复。

恢复流程:

mysql -u root -p < backup.sql

更专业的方案:全量备份用XtraBackup物理备份(速度快,不停机),binlog做增量备份,配合定期恢复演练。只备份不演练等于没备份,很多公司出事故恢复时才发现备份文件损坏,这一点面试时可以主动提出来,非常加分。

7.5 第30题:生产环境MySQL频繁重启或连接打满怎么排查

这是一个综合性问题。典型的排查链路:

  1. 看系统资源:topfree -hdf -h,确认CPU、内存、磁盘是否被耗尽了。
  2. 看数据库连接数:
SHOW STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections';

如果Threads_connected接近max_connections,说明连接泄漏或慢查询堆积。应用层应该检查连接池是否释放了连接。

  1. 看慢SQL和锁等待:
SHOW ENGINE INNODB STATUS\G SELECT * FROM performance_schema.data_lock_waits;
  1. 看错误日志:/var/log/mysql/error.log,确认是OOM重启还是InnoDB崩溃恢复。

连接被打满的常见原因是应用连接池配置过大,比如每个Java服务配置200个连接,部署20个实例,就是4000个连接,数据库怎么可能不炸?正确做法是控制服务实例数乘以连接池上限,给数据库留出冗余容量。

8. 后端开发中的MySQL最佳实践

面试不只是考答案,还考工程意识。以下几个实践习惯,建议每个后端都刻进DNA。

8.1 表结构设计规范

  • 必须有主键,优先自增或雪花ID,避免UUID主键导致B+树页分裂严重。
  • 字段全部设置NOT NULL并给默认值,避免NULL值导致索引统计不准确。
  • 金额用DECIMAL(18,2),不要用FLOAT和DOUBLE,避免精度问题。
  • 时间字段用DATETIME类型,不要用VARCHAR存时间。
  • 列名用下划线分隔能拼出语义的完整单词,避免缩写导致难以维护。
  • 每张表加create_timeupdate_time两个审计字段。
  • 大文本单独建表,避免主表行宽度过大,影响聚簇索引缓存效率。

8.2 SQL书写规范

  • 禁止SELECT *,只查需要使用的列。
  • 禁止在WHERE子句中对索引列使用函数和隐式类型转换。
  • UPDATE、DELETE语句必须带WHERE条件,并且先用SELECT确认影响行数。
  • 大批量变更数据要分批执行,每批500~1000条,中间加sleep,避免长事务和主从延迟。
  • 多表联查不要超过3张表,超过时考虑拆分查询。

8.3 事务控制规范

  • 事务尽量短,事务里不要发起远程HTTP调用或等待消息队列。
  • 使用@Transactional时要注意默认只对RuntimeException回滚,业务异常要先捕获并手动回滚。
  • 方法A调用方法B,且A上有事务、B也有事务,B的传播行为默认为REQUIRED,会加入A的事务。要跨服务独立事务时,需要自己实现。

8.4 索引维护规范

  • 冷数据表和低区分度列不要建索引。
  • 索引尽量在表创建初期设计完,后期加索引要锁表。MySQL 8.0支持INPLACE算法,但大批量还是建议低峰期执行。
  • 定期执行OPTIMIZE TABLEALTER TABLE FORCE来整理索引碎片。

9. 三天吃透这30题,面试怎么用

实战比背诵更重要。建议复习路径如下:

第一天:重点啃第1~10题。画一条SQL执行链路图,通过EXPLAIN对自己项目中的5条核心查询做一次真实分析,把typerowsExtra记录到笔记里。

第二天:重点啃第11~19题。结合项目中的事务场景,想一想你的代码中哪些地方存在长事务、哪些更新语句会产生锁等待。最好能在测试库模拟一次死锁,跑一次SHOW ENGINE INNODB STATUS

第三天:重点啃第20~30题。模拟慢查询,打开慢查询日志,观察日志输出。有余力的话,搭一个简单的一主一从环境,实际体验复制原理和SHOW SLAVE STATUS的输出。

面试时,如果被问到不会答的问题,不要硬编,可以坦诚说出你掌握的边界,然后补充思路:“这部分我没有在生产环境实际做过,但据我了解……”

这样做比一本正经的背诵更让面试官信服。

10. 总结:把面试题变成你的技术底色

单独背这30个问题的答案,撑不过三轮面试。但这些题目背后的原理——B+树索引、MVCC、锁、主从复制、分库分表——是一个后端工程师日常排查问题时真正要用的知识体系。

建议收藏本文,以三天为一个周期把每个问题过一遍,然后回到自己的项目中找到对应场景写一段代码或跑一条SQL去验证。面试不是靠题目押题押出来的,而是靠你真正理解数据库如何工作之后,无论面试官怎么变换问法,都能从底层原理推导出答案。MySQL这条路值得花时间走扎实,后端面试的难度会因此直线下降。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询