未记录事实核验 · 状态:维护中

记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。

warning 适用边界与维护说明

本文适用于高级MySQL面试的系统复习与追问准备,不是跨版本标准答案或可直接执行的生产运维手册。本次仅作全文结构与内部一致性审阅,未作外部事实核验或运行验证。Q10的VARCHAR长度前缀与临时内存解释、Q12的LIKE访问路径解释及Q47的子查询改写条件已有正文修正,不再作为未解决风险;当前仍需注意Q14混淆全表扫描与全索引扫描,Q35的固定N秒主库粘滞不能无条件保证后续写后读一致性,且复制等待写法缺少调用参数及成功判定。索引数量、选择性、内存比例等经验阈值与高可用方案的RTO分档应结合版本、工作负载和测试结果评估,不能视为通用保证。

MySQL面试题深度解析 - 50道高频题含答案

abstract 学习目标

  • 系统掌握MySQL面试的5大核心考察方向
  • 理解每道题背后的技术原理,而非死记答案
  • 形成完整的MySQL知识体系,应对追问

quote 核心洞察

好的面试答案不是背诵,而是展示你对技术本质的理解。当面试官问"为什么用B+Tree",他想听到的不是"B+Tree更好",而是:你知道磁盘I/O的成本,知道B+Tree在磁盘场景下的优势,知道B-Tree在这个场景下的劣势。技术深度体现在你能把"是什么"延伸到"为什么"和"有什么代价"。


第一类:基础概念(10题)

Q1. 为什么MySQL InnoDB使用B+Tree而不是B-Tree或哈希索引?

B-Tree每个节点存储data,导致单个磁盘页(16KB)容纳的键值更少,树高更大,磁盘I/O次数增多。B+Tree内部节点只存键值不存data,在页大小、索引键宽度和页填充率等条件合适时,千万级数据的B+Tree可能只有3-4层;实际磁盘I/O次数还取决于缓存命中和访问路径,不能由树高直接保证。B+Tree叶子节点通过双向链表连接,天然支持高效范围查询(直接遍历链表),而B-Tree的范围查询需要多次回溯。哈希索引只支持等值查询,不支持范围查询、排序、前缀匹配,适用场景极窄。

Q2. InnoDB和MyISAM的核心区别是什么?什么场景下会选MyISAM?

核心区别:InnoDB支持事务、行锁、外键、崩溃恢复(ACID);MyISAM不支持事务,使用表锁,但有FULLTEXT索引支持(MySQL 5.6前)。MyISAM的适用场景几乎已不存在——MySQL 5.6后InnoDB支持全文索引,MyISAM已无明显优势。极个别的读密集型、不需要事务的小表场景可以考虑MyISAM,但通常不推荐新项目使用。

Q3. MySQL的一条UPDATE语句的完整执行流程是什么?

  1. 连接器:验证用户身份和权限
  2. 解析器:词法+语法解析,生成解析树
  3. 优化器:选择最优执行计划(选择索引、JOIN顺序等)
  4. 执行器:调用InnoDB存储引擎接口
  5. InnoDB层:检查Buffer Pool中是否有该行,无则从磁盘读取到Buffer Pool
  6. 对内存中的行加行锁(X锁)
  7. 记录undo log(用于回滚)
  8. 修改Buffer Pool中的数据行(标记为脏页)
  9. 记录redo log(prepare状态)
  10. 写binlog
  11. 提交redo log(commit状态,两阶段提交完成)
  12. 脏页异步刷到磁盘(后台线程)

Q4. 什么是两阶段提交(2PC)?MySQL为什么需要它?

两阶段提交解决的是redo log和binlog之间的一致性问题。如果先写redo log再写binlog,binlog写入期间崩溃,则redo log有记录但binlog无记录,主从数据不一致。两阶段提交:先写redo log(prepare),再写binlog,再提交redo log(commit)。崩溃恢复时,若redo log是prepare状态且对应binlog存在,则提交;若binlog不存在,则回滚。

Q5. CHAR和VARCHAR的存储差异,以及如何选择?

CHAR(N)是定长存储,不足N个字符用空格填充,适合固定长度字符串(如国家代码、MD5哈希值),查询效率略高,不会产生碎片。VARCHAR(N)是变长存储,实际存储长度+1或2字节的长度前缀,适合长度变化较大的字段(如姓名、描述)。选择原则:长度固定且短(<10字符)选CHAR;长度变化大选VARCHAR;超长文本(>65535字节)选TEXT。

Q6. int(11)中的11是什么含义?

11是显示宽度(display width),与存储长度无关。INT类型始终占4字节,存储范围固定为-2147483648到2147483647。11只影响配合ZEROFILL属性时的显示宽度(填充0到11位)。MySQL 8.0已废弃整型的显示宽度属性。

Q7. 什么是覆盖索引?为什么能提升性能?

覆盖索引指查询所需的所有列都包含在索引中,无需回表(回到聚簇索引)获取完整行数据。性能提升原因:辅助索引叶子记录包含索引列和主键值,通常比完整行更紧凑;覆盖查询可减少回表及其潜在的随机I/O,但索引是否全部驻留Buffer Pool取决于索引大小和可用缓存。EXPLAIN中Extra列显示Using index表示命中覆盖索引。

Q8. NULL值在MySQL中的存储和比较有什么特殊性?

使用普通比较运算符与NULL比较时,结果为NULL(UNKNOWN),包括NULL = NULL;判断是否为空应使用IS NULL,进行NULL安全的相等比较可使用<=>。NULL值在B+Tree中单独存储在最左侧。COUNT(*)计算包含NULL的行,COUNT(col)排除NULL行。InnoDB和MyISAM的索引都可以包含NULL值。建议用NOT NULL + 默认值替代NULL,减少比较复杂性和存储空间(每个NULL列需要额外1bit存储NULL标志)。

Q9. 什么是最左前缀原则?为什么有这个规则?

联合索引按照列的顺序排序存储(先按第一列排序,相同时按第二列排序,以此类推)。跳过前面的列,后面的列就无法利用索引的有序性,因为后面列的值是相对于前面列排序的,直接查询后面列等于全表扫描索引。例如索引(a,b,c),WHERE b=2跳过了a,通常不能按普通最左前缀方式定位索引范围;但优化器仍可能选择索引全扫描,或在支持且满足条件时选择索引跳跃扫描,应以执行计划为准。

Q10. VARCHAR(10)和VARCHAR(100)的区别?

存储相同字符串时,字符内容所占字节相同,但总存储空间不一定相同:VARCHAR的长度前缀为1或2字节,取决于声明的最大字节长度是否超过255字节。例如使用utf8mb4时,VARCHAR(10)与VARCHAR(100)可能分别使用1字节和2字节长度前缀。临时表和排序的内存使用取决于MySQL版本、临时表引擎和执行方式,不能统一认定按声明长度分配。两者允许存储的最大字符数不同,且受字符集和行最大长度等条件限制。因此,应根据业务实际最大长度声明,不要随意声明过大的VARCHAR。


第二类:索引(10题)

Q11. 联合索引(a, b, c),以下哪些查询能走索引?

WHERE a=1                    -- 走索引(命中a)
WHERE a=1 AND b=2            -- 走索引(命中a,b)
WHERE a=1 AND b=2 AND c=3    -- 走索引(命中a,b,c)
WHERE b=2                    -- 不满足普通最左前缀定位条件;仍可能使用索引全扫描或符合条件的索引跳跃扫描
WHERE a=1 AND c=3            -- 通常用a定位;c仍可能用于索引条件过滤,是否回表及采用何种路径看执行计划
WHERE a=1 AND b>2            -- 走索引a和b范围,c不走
WHERE a=1 ORDER BY b         -- 走索引a,ORDER BY b利用索引排序

Q12. 为什么LIKE '%abc'不走索引,LIKE 'abc%'走索引?

B+Tree字符串索引按列的排序规则组织。LIKE 'abc%'在满足索引、表达式和排序规则等条件时,可以利用固定前缀形成索引范围;具体范围边界不能不加条件地写成从abc到abd。LIKE '%abc'通常无法利用普通B+Tree索引进行前缀范围定位,但覆盖查询仍可能采用索引全扫描,也可能选择全表扫描。因此,无法进行范围定位不等于完全不使用索引,应结合EXPLAIN判断实际访问路径。

Q13. 什么是索引下推(ICP)?举例说明其作用。

ICP(Index Condition Pushdown)是MySQL 5.6引入的优化:将WHERE条件中索引列的过滤下推到存储引擎层,减少回表次数。例如联合索引(name, age),查询WHERE name LIKE '张%' AND age > 20。无ICP:存储引擎只用name过滤,回表取全行,Server层再过滤age;有ICP:存储引擎同时过滤name和age,只有满足两个条件的行才回表。EXPLAIN中Extra显示Using index condition。

Q14. 如何判断一个查询是否需要新建索引?

步骤:1)EXPLAIN分析type列,若为ALL或index说明全表扫描;2)看rows估算扫描行数,过大需要优化;3)检查Extra,有Using filesort/Using temporary通常需要优化;4)用pt-query-digest分析慢日志,找出Rows_examined/Rows_sent比值过高的查询;5)在测试环境创建索引后再次EXPLAIN对比。考虑建索引的判断标准:查询频率高、过滤性强(选择性>10%)、无法通过改写SQL优化。

Q15. 索引对写性能的影响是什么?如何权衡?

每次INSERT/UPDATE/DELETE都需要维护所有索引的B+Tree(插入、删除节点,必要时分裂/合并页)。索引越多,写性能越低。权衡原则:根据查询频率决定,高频查询需要的索引优先;单表索引控制在5个以内;定期审查并删除从未被使用的索引(通过sys.schema_unused_indexes视图)。

Q16. 什么是前缀索引?有什么限制?

前缀索引只存储字符串列的前N个字符而非全部,节省索引空间。限制:1)无法作为覆盖索引使用(存储的是前缀,查询完整值仍需回表);2)不支持GROUP BY和ORDER BY使用前缀索引;3)需要通过计算选择性确定合适的前缀长度。

Q17. 聚簇索引和非聚簇索引在存储上有什么区别?

聚簇索引(InnoDB主键):叶子节点直接存储完整行数据,数据物理顺序与索引顺序一致,每个表只能有一个。非聚簇索引(辅助索引):叶子节点存储主键值,查询需要先获取主键再回表,存储空间小,一个表可以有多个。这导致主键选择很重要:选择单调递增的主键(如AUTO_INCREMENT)可以避免B+Tree频繁页分裂,提升写入性能。

Q18. 为什么不建议使用UUID作为MySQL主键?

UUID是随机字符串,无序。InnoDB的数据按主键顺序存储(聚簇索引),插入随机UUID时,大量数据需要插入到B+Tree的中间位置,频繁触发页分裂(将一个页的数据分到两个页),导致:1)写性能下降;2)索引碎片化,空间利用率降低;3)UUID占用36字节(vs BIGINT的8字节),所有辅助索引都存储主键,空间开销更大。推荐使用自增ID(AUTO_INCREMENT)或雪花算法生成的趋势递增ID。

Q19. 如何强制MySQL使用或忽略某个索引?

-- 强制使用某索引
SELECT * FROM orders FORCE INDEX(idx_user_id) WHERE user_id = 100;

-- 建议使用某索引(优化器仍可能忽略)
SELECT * FROM orders USE INDEX(idx_user_id) WHERE user_id = 100;

-- 忽略某索引
SELECT * FROM orders IGNORE INDEX(idx_user_id) WHERE user_id = 100;

仅在诊断或紧急处理时使用,不推荐长期在业务代码中使用(升级MySQL版本后优化器行为可能改变)。

Q20. Explain中key_len如何计算?有什么用途?

key_len表示实际使用的索引字节数。计算规则:INT=4字节(NOT NULL)/5字节(允许NULL);VARCHAR(N) utf8mb4 = N×4+2字节(变长);DATE的基础存储长度为3字节;MySQL 5.6.4及之后的DATETIME基础存储长度为5字节,小数秒精度可能增加字节,旧格式为8字节;计算key_len还需考虑可空标记等因素。通过key_len可以判断联合索引实际使用了几列:对于(user_id INT, status TINYINT)索引,若两列都允许NULL,则key_len=5对应user_id,key_len=7对应两列;若两列都为NOT NULL,对应长度为4和5字节。


第三类:事务与锁(10题)

Q21. 什么是MVCC?InnoDB如何用它实现不同隔离级别?

MVCC(多版本并发控制)通过维护数据的多个版本,让读操作不阻塞写操作(也不被写操作阻塞),大幅提升并发性能。实现机制:每行数据有隐藏字段(DB_TRX_ID最后修改的事务ID、DB_ROLL_PTR指向undo log的指针),修改时不覆盖旧版本而是创建新版本形成版本链;事务读取时通过Read View确定可见性:RC隔离级别每次SELECT创建新Read View(能读到已提交的最新数据);RR级别只在第一次SELECT时创建Read View(整个事务看到快照一致性视图)。

Q22. 什么是幻读?InnoDB在RR级别如何处理幻读?

幻读:同一事务内,两次相同范围查询,第二次出现第一次没有的行(其他事务插入了新行)。InnoDB用两种手段处理:快照读(普通SELECT)通过MVCC的Read View机制避免幻读,事务始终看到第一次SELECT时的快照;当前读(SELECT...FOR UPDATE等)通过临键锁(Next-Key Lock)锁定索引范围,阻止其他事务在该范围插入新行。严格来说,RR级别无法完全消除所有幻读场景(快照读+当前读混用时仍可能出现),Serializable级别才能完全消除。

Q23. 行锁、间隙锁、临键锁各是什么?有什么区别?

Record Lock(行锁):锁定索引上的单条记录,防止其他事务修改。Gap Lock(间隙锁):锁定索引记录之间的空隙(不包括记录本身),防止插入,纯粹防止幻读。Next-Key Lock(临键锁):Record Lock + Gap Lock的组合,锁定一条记录及其之前的间隙,是InnoDB RR级别的默认锁类型。实际加哪种锁取决于查询条件和索引类型:唯一索引等值查询退化为Record Lock;普通索引等值和范围查询使用Next-Key Lock。

Q24. 什么情况下会发生死锁?如何预防?

死锁四个必要条件:互斥(锁不能共享)、不可剥夺(锁不能强制释放)、请求与保持(持有锁的同时等待其他锁)、循环等待(多个事务形成锁等待环)。预防策略:1)统一所有事务的资源访问顺序;2)缩小事务范围,减少锁持有时间;3)避免长事务(尤其是包含外部调用的事务);4)用乐观锁替代悲观锁(低冲突场景);5)在业务允许时将隔离级别降至RC,减少常规搜索和扫描中的间隙锁;外键检查和重复键检查仍可能使用间隙锁。

Q25. redo log和binlog有什么区别?为什么需要两者都存在?

redo log是InnoDB存储引擎层的物理日志,记录对数据页的修改,循环写,用于崩溃恢复(保证持久性)。binlog是MySQL Server层的逻辑日志,记录SQL或行变更,追加写,用于主从复制和数据归档。为什么需要两者:redo log用于单机崩溃恢复,binlog用于主从数据同步。如果只有redo log,无法做主从复制;如果只有binlog,无法保证崩溃后数据完整性(binlog在事务期间不记录中间状态)。两者通过两阶段提交(2PC)保持一致性。

Q26. 什么是乐观锁?如何用version字段实现?

乐观锁不是数据库锁,而是应用层并发控制策略。假设冲突概率低,不加锁直接操作,提交时检查数据是否被修改。实现:在表中增加version字段,读取时记录version值,更新时携带version条件:UPDATE t SET val=new, version=version+1 WHERE id=1 AND version=old_version;若affected_rows=0说明被其他事务修改,需要重试。适用于读多写少、冲突概率低、重试代价可接受的场景。

Q27. RC和RR隔离级别在加锁行为上有什么主要差异?

最关键的差异:RC下常规搜索和扫描通常不使用间隙锁,但外键检查和重复键检查仍有例外;RR下范围锁定通常使用临键锁,具体锁范围取决于索引、查询条件和访问路径。这意味着:RC级别下INSERT并发度更高(间隙锁是INSERT的主要竞争点);RC级别下死锁概率更低;但RC级别下同一事务内两次相同范围查询可能返回不同结果(允许不可重复读和幻读)。对于很多业务场景(尤其是高并发写入),RC级别是更好的选择。

Q28. 如何查看和分析死锁信息?

SHOW ENGINE INNODB STATUS\G

关注LATEST DETECTED DEADLOCK部分,包含:死锁发生时间、涉及的两个事务ID、每个事务等待的锁类型和涉及的行、被回滚的事务。更系统的方式:开启innodb_print_all_deadlocks=ON将所有死锁写入错误日志;或通过performance_schema.data_locks实时查看当前锁状态。

Q29. 什么是长事务?为什么它危险?如何监控?

长事务:运行时间超过预期的事务(通常指超过几秒甚至几分钟的事务)。危险原因:1)持有锁时间长,阻塞其他事务;2)MVCC需要保留整个事务期间所有版本的undo log,不能及时回收,导致undo log膨胀,消耗大量磁盘空间;3)主从复制中,从库必须等长事务完全结束才能开始重放,导致主从延迟。监控:SELECT * FROM information_schema.INNODB_TRX WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60。

Q30. SAVEPOINT有什么用途?

SAVEPOINT允许在事务内设置保存点,回滚到某个保存点而非回滚整个事务,实现"部分回滚":

BEGIN;
INSERT INTO t VALUES(1);
SAVEPOINT sp1;
INSERT INTO t VALUES(2);  -- 假设这里出错
ROLLBACK TO sp1;          -- 只回滚sp1之后的操作,VALUES(1)保留
COMMIT;

适用于复杂业务逻辑中需要条件性局部回滚的场景。


第四类:高可用(10题)

Q31. MySQL主从复制的流程是什么?涉及哪些线程?

流程:主库在事务提交过程中按两阶段提交协调binlog与InnoDB redo log;主库的Binlog Dump Thread检测到新binlog,发送给从库;从库的IO Thread接收binlog并写入relay log;从库的SQL Thread(或并行复制的Worker Threads)读取relay log并在从库重放。三类线程:主库的Binlog Dump Thread,从库的IO Thread(拉binlog)和SQL Thread(重放)。

Q32. 主从延迟的常见原因有哪些?如何解决?

常见原因:1)主库并发写入,从库单线程串行重放(主要原因);2)大事务(批量DELETE/UPDATE)在从库重放耗时;3)DDL操作在从库阻塞复制;4)网络带宽不足;5)从库磁盘I/O差。解决方案:开启并行复制(slave_parallel_type=LOGICAL_CLOCK, slave_parallel_workers=N);业务层避免大事务;DDL在维护窗口执行;从库使用SSD;监控Seconds_Behind_Master,设置告警阈值。

Q33. 什么是半同步复制(Semi-sync Replication)?

标准主从复制是异步的:主库提交事务后立即返回,不等待从库确认。半同步复制:主库提交事务后,等待至少一个从库确认已将binlog写入relay log,才返回客户端成功。优点:主库宕机时,至少有一个从库拥有完整的binlog,数据丢失窗口大幅缩小。代价:每次事务增加一次网络RTT延迟(通常1-5ms)。超时后自动降级为异步复制。

Q34. MGR(Group Replication)和传统主从复制有什么区别?

传统主从:异步/半同步,手动或工具(MHA)做故障转移,故障转移期间可能有数据丢失。MGR:通过组通信和事务认证协调成员,可在单主模式下自动选举新的Primary,无需MHA承担组内选主;客户端重连和流量路由仍需配套设计。事务提交并不默认保证所有成员已经应用事务,读取一致性取决于一致性配置和访问方式。代价:多节点通信可能增加写延迟,大事务会影响组内处理。可将30秒切换和节点间延迟小于5ms作为待验证的目标或假设,而非产品保证;跨机房可行性需结合网络、故障检测参数和负载评估。

Q35. 什么是读写分离?如何保证写后读的一致性?

读写分离:写操作路由到主库,读操作路由到从库(一个或多个),降低主库读负载。一致性问题:由于主从异步,写入主库后立即从从库读,可能读到旧数据("写后读"问题)。解决方案:1)写后N秒内同一用户读操作强制走主库(Session sticky);2)SELECT MASTER_POS_WAIT等待从库追上;3)半同步复制可缩小日志丢失窗口,但接收确认不代表从库已应用事务,不能单独保证写后读一致性;4)关键业务直接读主库(如支付确认页面)。

Q36. GTID是什么?它对主从复制有什么改善?

GTID(Global Transaction Identifier):每个事务的全局唯一标识,格式为source_id:transaction_id。改善:1)从库切换主库时无需手动指定binlog文件名和位点,自动找到未执行的GTID继续同步;2)便于监控复制状态,比较主从GTID集合差距;3)避免主从切换后的数据重复执行问题。启用GTID后的语句限制取决于MySQL版本、存储引擎及相关配置;CREATE TABLE ... SELECT在旧版本存在限制,MySQL 8.0.21及之后对支持原子DDL的存储引擎已支持该语句,不能笼统认定启用GTID后均不可用。

Q37. 什么是MHA?它的工作原理是什么?

MHA(Master High Availability Manager):开源MySQL主从切换工具。工作原理:MHA Manager节点定期检测主库心跳;主库超时后,比较所有从库的relaylog位点,找到最新的从库;将最新从库的binlog差异同步到其他从库;提升最新从库为新主库;其他从库指向新主库;VIP漂移到新主库。切换时间:通常30秒到3分钟,取决于网络和数据差异大小。限制:MHA Manager本身是单点(通常通过keepalived解决)。

Q38. 如何实现MySQL的在线平滑主从切换(不丢数据)?

计划切换的原则流程:1)冻结旧主写入并处理在途事务,必要时在受控连接中执行FLUSH TABLES WITH READ LOCK;2)记录冻结后的最终binlog位点或GTID集合;3)保持从库接收和应用复制日志,确认已完整接收并应用至该位点或事务集合,不能只看Seconds_Behind_Master=0;4)隔离旧主写入,停止候选新主的复制并按部署配置解除只读、切换写流量;5)保留binlog和GTID历史,不将RESET MASTER作为通用提升步骤;6)将旧主配置为新主的从库并验证同步后再开放相应流量。具体命令依MySQL版本和复制拓扑调整。ProxySQL与Orchestrator可辅助路由和切换自动化,但是否无感及是否无损仍需验证。

Q39. 什么是Binlog Server?有什么作用?

Binlog Server(如MySQL Binlog API、Ripple)是一个独立的binlog接收和存储服务,伪装成MySQL从库订阅主库binlog,但不重放,只存储。作用:1)提供额外的binlog备份点;2)多个从库可以从Binlog Server拉取binlog,减轻主库的Binlog Dump压力;3)主库宕机后,从Binlog Server可以获取未传输到其他从库的最新binlog。

Q40. 如何选择MySQL的高可用方案?

选择维度:

  • 单机应用 → 不需要HA,做好备份
  • 简单主从(允许手动切换,RTO>5min) → 异步主从 + MHA
  • 需要快速自动切换(RTO<1min) → InnoDB Cluster(MGR + Router)
  • 超高写入并发,需要水平扩展 → 分库分表(ShardingSphere)+ 每分片独立HA
  • 云上部署 → 直接用云厂商RDS高可用版(Aurora/PolarDB等),避免自建运维复杂度

第五类:性能优化(10题)

Q41. 如何定位MySQL中的慢查询?

三步走:1)开启慢查询日志(long_query_time=1秒),收集生产环境真实慢查询;2)用pt-query-digest分析慢日志,按总执行时间排序找Top10;3)对每条慢查询执行EXPLAIN,分析type/key/rows/Extra,找出问题(全表扫描/filesort/临时表等)。快速定位:SHOW PROCESSLIST查看当前正在执行的查询;performance_schema.events_statements_summary_by_digest查看历史查询统计。

Q42. innodb_buffer_pool_size如何设置?

Buffer Pool是InnoDB最重要的内存参数,存储数据页、索引页、undo等。设置原则:专用MySQL服务器设为物理内存的70-80%(预留给OS和其他进程);Buffer Pool命中率(1 - Innodb_buffer_pool_reads/Innodb_buffer_pool_read_requests)低于95%时增大。实际计算:64GB内存服务器设置48GB;可以通过innodb_buffer_pool_instances按1GB/instance分割(降低内部竞争)。MySQL 5.7+支持在线动态调整,无需重启。

Q43. 什么情况下MySQL会选择全表扫描而不是索引?

优化器选择全表扫描通常是因为:1)预估走索引的代价(回表次数×随机I/O代价)高于全表扫描代价——这种判断可能是正确的;2)索引统计信息不准确(ANALYZE TABLE更新统计信息);3)强制使用FORCE INDEX可以验证问题。常见触发场景:索引列选择性极低(status只有0/1)、查询需要返回大比例行(>20-30%)、小表(全表扫描在Buffer Pool中反而更快)。

Q44. 深度分页问题如何优化?

深度分页(LIMIT 990000, 10)需要扫描和丢弃前990000行,极其低效。优化方案:1)游标分页:将上一页最后一条记录的排序字段值作为下一页查询的WHERE条件,完全避免offset;2)延迟关联:先通过覆盖索引获取主键(无需回表),再根据主键取完整行;3)禁止跳页(产品层面),只允许顺序翻页;4)ElasticSearch替代(全文搜索场景)。

Q45. 如何优化一条执行很慢的GROUP BY查询?

步骤:1)EXPLAIN查看Extra,若有Using temporary; Using filesort说明无法利用索引分组;2)检查GROUP BY的列是否有合适索引;3)若WHERE和GROUP BY列能构成联合索引(WHERE列在前,GROUP BY列在后),可以避免临时表;4)检查是否可以改写为覆盖索引(避免回表);5)考虑是否将统计结果预先计算存储(汇总表模式);6)大数据量GROUP BY考虑移至数仓(OLAP层)处理。

Q46. 分库分表后如何处理全局唯一ID?

MySQL AUTO_INCREMENT在分库分表后不再全局唯一(每个分片的AUTO_INCREMENT独立)。解决方案:1)雪花算法(Snowflake):64位ID由时间戳(41位)+机器ID(10位)+序列号(12位)组成,趋势递增,每毫秒可生成4096个ID;2)数据库号段:专用ID生成表,每次批量取一段(如每次取1000个),减少数据库访问;3)Redis原子自增(INCR);4)UUIDBinHex(16字节二进制存储,节省空间但仍随机)。推荐雪花算法,兼顾性能和有序性。

Q47. 如何优化一条包含多个子查询的复杂SQL?

1)结合执行计划评估关联子查询是否存在重复执行开销,再考虑改写为JOIN;优化器可能对部分子查询进行转换或物化,JOIN也不保证只执行一次或必然更快;2)评估IN子查询改写为JOIN或EXISTS的可行性,保持原有重复行和NULL语义,避免JOIN因多个匹配行而放大结果,并比较改写前后的执行计划与实际耗时;3)将重复的子查询提取为WITH(CTE,MySQL 8.0支持)复用;4)检查各子查询是否命中索引;5)考虑是否可以通过增加冗余字段(反范式化)消除JOIN;6)复杂报表查询考虑预聚合(汇总表/物化视图)。

Q48. 什么是自适应哈希索引(AHI)?

InnoDB自动为热点查询(频繁访问的B+Tree节点)建立内存哈希索引,将B+Tree的O(logN)查询转换为O(1)的哈希查询。由InnoDB自动管理,无需手动干预。在某些高并发等值查询场景下提升明显,但也有维护开销。可以通过innodb_adaptive_hash_index参数控制开关(某些特定场景下关闭AHI反而有提升,需实测)。

Q49. MySQL 8.0相比5.7有哪些值得关注的性能改进?

关键改进:1)默认utf8mb4字符集(解决表情符号乱码);2)移除查询缓存(Query Cache,高并发下是性能瓶颈);3)降序索引真正支持(不再假降序);4)隐藏索引(Invisible Index,可安全测试删除索引的影响);5)函数索引(对表达式建索引);6)Hash Join(大幅提升无索引关联的性能);7)并行查询需区分发行版和具体实现,不能作为MySQL 8.0社区版通用SQL查询能力概括;8)角色(Role)权限管理;9)窗口函数(分析函数)原生支持。

Q50. 如果MySQL实例CPU持续飙高100%,如何快速定位和处理?

应急步骤:1)立即执行SHOW PROCESSLIST,找出Time很长的运行中查询;2)KILL <thread_id>停止正在消耗CPU的查询(评估业务影响后操作);3)执行SHOW ENGINE INNODB STATUS\G查看是否有大量锁等待或死锁;4)先确认当前版本及发行版的performance_schema.events_statements_summary_by_digest是否提供并采集SUM_CPU_TIME,可用时按该列排序定位CPU消耗;不可用时结合语句执行时间、执行次数和操作系统线程CPU指标分析,执行时间不等同于CPU时间;根本原因通常是:全表扫描的高频查询、大量慢查询堆积、严重锁竞争、批量ETL与业务查询竞争资源。处理完应急后,通过慢查询日志和EXPLAIN进行根本性优化。


备考建议

tip 面试备考策略

这50道题覆盖了MySQL面试的核心考点。建议:

  1. 理解原理,不要背答案——面试官必然会追问
  2. 每个知识点都要能举出实际业务场景的例子
  3. 对性能类问题,要能给出量化判断(几行数据,什么量级考虑什么方案)
  4. 了解技术的边界和局限性,不要把一个技术讲成"万能解决方案"

关联知识