场景模拟:一场技术面试的攻防战
面试官:你在简历里提到优化过大量SQL查询,那你说说为什么MySQL做关联查询时建议用小表驱动大表?
候选人:因为用小表作为驱动表可以减少外层循环的次数,比如用INNER JOIN
时,外层表行数越少,内层表扫描次数就越少,性能更好。
面试官(追问):那什么是"驱动表"?MySQL是如何决定哪个表作为驱动表的?
候选人:驱动表是执行计划中首先被访问的表,决定了连接顺序。MySQL优化器会根据统计信息(如索引、行数、数据分布)自动选择。但我们可以用STRAIGHT_JOIN
强制指定驱动表。
面试官(深入):假设两个表都没有索引,为什么这时候"小表驱动大表"的性能差异会更明显?
候选人:如果被驱动表无索引,内层循环每次都要全表扫描。假设驱动表有N行,被驱动表有M行: • 小表驱动(N=100, M=1万):总扫描次数 = 100次全表扫描(1万行)→ 100万行
• 大表驱动(N=1万, M=100):总扫描次数 = 1万次全表扫描(100行)→ 100万行
虽然总行数相同,但前者需要更多磁盘I/O(大表数据分散),且可能触发缓存淘汰。
面试官(陷阱题):那如果被驱动表有索引,小表驱动还有必要吗?
候选人:此时性能差异可能缩小,但仍有优化空间。例如: • 小表驱动时,内层走索引查询,每次查找是O(log M)
复杂度。
• 大表驱动时,内层虽然也走索引,但外层循环次数更多,CPU开销可能更高。
此外,驱动表数据量小,更容易放入join_buffer
(若使用Block Nested-Loop Join算法)。
面试官(底层原理):能解释下Block Nested-Loop Join(BNL)和Index Nested-Loop Join(INL)的区别吗?
候选人: • INL:被驱动表有索引时,内层循环直接通过索引定位数据,时间复杂度O(N log M)
。
• BNL:无可用索引时,将驱动表加载到join_buffer
,批量匹配被驱动表,时间复杂度O(N * M)
。此时用小表驱动可降低内存占用,减少磁盘扫描次数。
面试官(实战场景):如何判断一条关联查询是否使用了最优驱动表?
候选人:通过EXPLAIN
查看执行计划: • 第一行出现的表即为驱动表。
• 关注type
字段:ref
/range
表示索引有效,ALL
表示全表扫描。
• 检查rows
预估行数,对比实际数据量判断统计信息是否准确。
核心结论
-
性能本质:减少外层循环次数 + 利用索引降低内层循环代价。
-
优化器局限:统计信息过期、复杂过滤条件可能导致优化器误判。
-
判断标准: • 当被驱动表无索引 → 严格遵循"小表驱动大表"。
• 当被驱动表有索引 → 优先保证内层循环走索引,驱动表选择次之。
-
终极方案:通过
EXPLAIN
验证执行计划,必要时用FORCE INDEX
或STRAIGHT_JOIN
干预。
面试官:不错,今天先聊到这儿。回去等通知吧。(暗中点头)
附录:经典优化公式
总查询代价 ≈ 驱动表查询成本 + 驱动表行数 × 单次被驱动表查询成本
通过降低驱动表的行数
或降低被驱动表的单次查询成本
(如加索引),可显著提升性能。