🛢 MySQL+MyBatis 优化面试10题
阅读量关键词: EXPLAIN执行计划 | 索引失效场景 | MyBatis N+1 | 慢SQL定位 | 批量优化 | 分库分表
Q1: EXPLAIN执行计划怎么看?type字段优先级是什么?
标准答案:
EXPLAIN是MySQL性能诊断的瑞士军刀。核心关注4个字段:
•
type(访问类型):
system > const > eq_ref > ref > range > index > ALL
•
key:实际使用的索引名
•
rows:预估扫描行数
•
Extra:额外信息——Using filesort(需优化)、Using temporary(严重)、Using index(覆盖索引,好)
EXPLAIN SELECT * FROM orders WHERE user_id=100 AND status='paid';
-- type: ref ✅
-- key: idx_user_status ✅
-- rows: 10 ✅
Q2: MySQL索引失效的10种场景?
背这10条,面试够用了:
① LIKE '%keyword' — 左模糊
② 联合索引不满足最左前缀
③ WHERE子句中对索引列使用函数 WHERE YEAR(date)=2026
④ 隐式类型转换 WHERE phone=13800138000(phone是varchar)
⑤ OR条件中有非索引列
⑥ != 或 <> 操作符
⑦ IS NULL / IS NOT NULL(视数据分布)
⑧ 范围查询右边的列(联合索引 a_b_c,WHERE a=1 AND b>2 AND c=3 → c不走索引)
⑨ 不使用索引列做计算 WHERE age+1=20
⑩ 全表扫描更快时MySQL自动放弃索引(数据量小时)
Q3: 怎么定位和优化一条慢SQL?
四步法:
① 开启慢查询日志 SET GLOBAL slow_query_log=ON; SET long_query_time=0.5;
② 抓出慢SQL mysqldumpslow -s t -t 10 slow.log
③ EXPLAIN分析 看type/rows/Extra
④ 针对性优化:加索引→改SQL→拆表→加缓存
举例:发现 SELECT * FROM order WHERE status='pending' ORDER BY create_time 走了 filesort→加联合索引 idx_status_time(status, create_time) → rows从50万降到200,耗时从3s降到50ms。
Q4: MyBatis怎么解决N+1查询问题?
N+1场景:查订单列表→循环查每个订单的明细→100条订单发101条SQL。
三种解决方案:
①
嵌套查询+延迟加载: <association fetchType="lazy"> — 需要时再查,减少首屏SQL
②
JOIN直接查: 一条SQL LEFT JOIN搞定,适合关联数据少的场景
③
批量查询(推荐): 先查订单→收集所有order_id→一次IN查所有明细→Java层组装
Q5: MyBatis #{}和${}区别?为什么SQL注入?
核心区别:
•
#{}:预编译占位符 →
SELECT * FROM user WHERE id=? → 防注入 ✅
•
${}:字符串拼接 →
SELECT * FROM user WHERE id=1 OR 1=1 → 危险 ❌
${}何时用? 动态表名、ORDER BY字段名——必须用且必须做白名单校验。
// ❌ 危险
SELECT * FROM ${tableName} WHERE id=#{id}
// 攻击者传 tableName="user; DROP TABLE user--"
// ✅ 安全:白名单校验
if (!ALLOWED_TABLES.contains(tableName)) throw...
Q6: MyBatis批量插入怎么优化?
三种方案,性能递增:
①
foreach拼接(推荐500条内)
INSERT INTO user(name,age) VALUES
(#{u.name}, #{u.age})
②
ExecutorType.BATCH 批量提交 — 适合超大量,MyBatis内置
③
原生JDBC addBatch — 终极方案,绕过MyBatis一层开销
实测数据:10000条插入→foreach 8s → BATCH 2s → JDBC addBatch 1.2s
Q7: MySQL事务隔离级别有哪些?MVCC怎么实现的?
4个隔离级别: READ UNCOMMITTED → READ COMMITTED → REPEATABLE READ(InnoDB默认) → SERIALIZABLE
MVCC核心原理:
• 每行数据隐藏列:DB_TRX_ID(最后修改事务ID) + DB_ROLL_PTR(回滚指针指向上一个版本)
• Undo Log版本链:每次更新产生旧版本,串联成链
• ReadView快照:事务开始时生成,根据事务ID判断可见性
• RC级别:每次SELECT重新生成ReadView
• RR级别:事务内第一次SELECT生成ReadView,之后复用
Q8: 分库分表怎么做?常用方案对比?
分片策略:
• 垂直拆分: 按业务模块(订单库/用户库)— 简单,但单表仍可能大
• 水平拆分: 按哈希(user_id%16)或范围(按时间)拆多张表
常用中间件:
• ShardingSphere-JDBC: 客户端分片,无代理层,性能好
• MyCat: 代理层,跨语言支持但多一跳
• Vitess: Google开源,适合超大规模
核心问题: 分布式ID(雪花算法)、跨库事务(Seata)、跨库JOIN(应用层聚合)
Q9: 覆盖索引是什么?怎么利用它优化?
覆盖索引: SELECT的列都在索引中,不需要回表查聚簇索引。
-- ❌ 需要回表(name不在索引里)
CREATE INDEX idx_age ON user(age);
SELECT name FROM user WHERE age=20; -- 还要去主键索引找name
-- ✅ 覆盖索引
CREATE INDEX idx_age_name ON user(age, name);
SELECT name FROM user WHERE age=20; -- Extra: Using index ✅
实战技巧: 用
EXPLAIN 看 Extra 列是否出现
Using index。高频查询尽量设计覆盖索引。
Q10: 千万级大表怎么平滑迁移?
四步平滑迁移法:
① 双写阶段: 新老表同时写,读老表
② 历史数据搬迁: 分批次迁移(每次1000条),不锁表
③ 校验数据: 逐条对比或抽样校验一致性
④ 切读: 灰度切流(1%→10%→100%),有问题随时回滚
不会丢数据: 双写+定时对账+消息队列重试。面试说出这4步就已经是专家水平。
☕ 帮到你了?请我喝杯咖啡
这些面试题来自真实面经整理,持续更新中
微信扫码 · 金额随意 · 你的支持是我更新的动力
📊 面试模拟器 |
📝 简历生成器 |
🔍 简历诊断 |
📚 更多文章