34.索引 + 不等号 + 范围查询:怎么设计才能不被优化器“背刺”?

索引 + 不等号 + 范围查询:怎么设计才能不被优化器“背刺”?

题目关键词:联合索引 + 不等号(!=)+ 范围查询(> / < / between)+ 覆盖索引
面试官真正想看的是:你是否理解 MySQL B+Tree 的扫描特性,能否设计出既高效又稳定的索引方案

一、题目里的坑:不等号和范围条件怎么排?

很多人看到条件组合,会随手按“业务重要性”或者“字段顺序”去建联合索引,例如:

  • 条件里既有 c != ?,也有 a > ?,还要查 b
  • 很容易乱写成 INDEX(a, c, b)INDEX(c, a, b),但顺序不对,性能直接崩

直觉上的几个误区:

  • 认为“不等号”也能很好利用索引过滤 → 实际效果通常很差。
  • 认为“条件里写了什么就按什么顺序建索引” → 完全忽略了 B+Tree 的扫描特性。
  • 不考虑“是否覆盖索引”,导致频繁回表。

二、三个核心结论先记住

在这类题里,通常有一条典型联合索引:INDEX(c, a, b),然后两个 SQL 都可以走这个索引。

面试时可以先抛出结论:

  1. 范围查询要优先能用上索引> / < / BETWEEN!= 容易被优化器利用)。
  2. 不等号(<> / !=)放后面,一般不适合作为前导列
  3. 覆盖索引最香:能在二级索引里拿齐所需字段,就不用回表。
  4. 不等号条件上的索引几乎等于废掉:会导致大范围扫描,尽量避免。

图书馆类比:

  • 找“不是红色封面的书”(不等号):得把所有书翻一遍。
  • 找“2025 年之后出版的书”(范围):直接去新书区。

三、联合索引为什么要这么设计?

假设业务里有两条典型查询:

  • 查询 1:包含范围条件 + 其他精确条件。
  • 查询 2:类似条件组合,但过滤更精准。

通过 EXPLAIN 你会观察到:

  • 两个查询都能走 INDEX(c, a, b)
  • 扫描行数差异很大:
    • 查询 2 只扫了大约 6 行;
    • 查询 1 却扫了 900 多行;
  • FORCE INDEX 也没能改善,因为本质是条件本身筛选度不好

这里可以强调两点:

  • 优化器只能在既有索引结构下“尽量聪明”,结构设计错了,FORCE INDEX 也救不了你。
  • 范围 + 不等号混用时,索引列顺序非常关键,否则会出现“条件写得很努力,扫描依旧全表级”的尴尬局面。

四、覆盖索引:为什么“目录上就要有答案”?

继续用图书馆类比:

  • 普通索引:
    • 先查目录(索引树)找到对应书的位置;
    • 再去书架把书拿下来翻(回表)。
  • 覆盖索引:
    • 目录上就有你要的所有信息;
    • 看完目录直接走人,不用再去书架。

设计联合索引时,把查询用到的字段全部放进索引(哪怕有的只是放在尾部),可以极大减少回表 IO,这就是“覆盖索引最香”的原因。


五、面试答题节奏

  1. 先复述题意 + 几个关键点

    • 联合索引 + 不等号 + 范围查询 + 覆盖索引。
  2. 直接给出一套推荐设计

    • 比如:INDEX(c, a, b),并说明“这样可以同时兼顾范围过滤 + 覆盖索引”。
  3. 讲清 3~4 个原则

    • 范围查询放在能被利用的位置。
    • 不等号慎用做前导列。
    • 尽量让热点查询变成覆盖索引。
    • 不要指望 FORCE INDEX 修补索引设计错误。
  4. 最后用图书馆类比收尾

    • 不等号 → 把书全翻一遍。
    • 范围查询 → 直接去新书区。
    • 覆盖索引 → 目录上就有所有信息。

九种方式控制线程执行顺序:怎么答才能“秒杀面试官”?

问题:在 Java 中如何控制多个线程的执行顺序?
面试官往往不会满足于你说 1、2 种,他们会一路追问:“还有吗?还有吗?”
这题考察的是:你对 JUC 工具、线程池、阻塞队列等的整体掌握程度

一、答题策略:分类而不是背口诀

不要机械背“9 大方案”,建议按类型分层回答:

  • 基础类join、单线程池。
  • 协作类CountDownLatchCyclicBarrierSemaphoreReentrantLock+Condition
  • 异步编排类CompletableFuture
  • 阶段/分步类Phaser
  • 消息传递类:阻塞队列(BlockingQueue)。

这样既显得体系化,又能随时深入其中一两种细讲。


二、九种方案快速过一遍(带关键词)

  1. Thread.join() 链式等待

    • 让当前线程等待目标线程结束。
    • 通过 t1.join(); t2.join(); 等形成依赖链,确保 A→B→C 顺序。
  2. CompletableFuture 链式编排

    • 使用 thenRun/thenAccept/thenCompose 自然形成前后依赖。
    • 天然支持顺序执行和组合,并可拓展为并行 + 汇总。
  3. CountDownLatch 计数器

    • 通过计数器归零来唤醒后续线程。
    • 可以让“后一个线程 await 上一个线程/一批线程”的完成。
  4. CyclicBarrier 分阶段栅栏

    • 多线程在一个屏障点相互等待,全部到齐再一起进入下一阶段。
    • 每一阶段结束后触发下一阶段执行。
  5. Semaphore 信号量

    • 控制“允许通过的线程数”,初始化为 0 时可以“全部先阻塞”。
    • 前一个线程执行完 release(),再允许下一个 acquire() 成功。
  6. 单线程线程池(Executors.newSingleThreadExecutor()

    • 所有提交的任务按队列顺序串行执行。
    • 间接保证了任务执行顺序。
  7. ReentrantLock + Condition 精准唤醒

    • 每个线程挂在一个单独的 Condition 上。
    • 执行完某个线程后,用 signal() 唤醒指定下一个线程,实现精确顺序。
  8. Phaser 分阶段同步器

    • 更灵活的多阶段同步工具,支持动态注册/注销线程。
    • 所有参与者完成当前阶段后,一起进入下一阶段。
  9. 阻塞队列(BlockingQueue)消息传递

    • 利用队列“先入先出”的特性,通过传递令牌/消息来严格控制先后。
    • 生产者-消费者场景里天然带顺序控制语义。

三、怎么答才显得“既多又不乱”?

建议这样组织:

  1. 先说 2~3 个基础且常用的

    • join、单线程池、CountDownLatch
  2. 再说 2~3 个 JUC 工具类控制顺序

    • CyclicBarrierSemaphoreReentrantLock+Condition
  3. 加 2 个“稍微高阶一点”的

    • CompletableFuture(异步编排)、Phaser 或阻塞队列。

最后用一句话收尾:

“本质上是通过‘等待/唤醒/排队/消息传递’四种手段来约束线程执行时机,具体选哪种,看是需要简单依赖,还是阶段协同,还是生产者消费者场景。”


三、MySQL binlog:一支“流水账”撑起高可用

面试题:说说你对 MySQL binlog 的理解?
真正考察的是:你能否用通俗类比 + 清晰结构,把 binlog 的用途讲明白。

一、binlog 是什么?

可以直接用你原话的比喻:

binlog 就是 MySQL 的“流水账本”,只记一件事:所有改动数据的操作(增删改),
纯查询操作不记。

这句话既形象又准确。


二、binlog 的三大核心作用

  1. 主从复制的基石

    • 主库把所有数据变更按顺序记到账本里(binlog);
    • 从库去订阅这本账,照着一条条重放操作;
    • 这样主从就能做到数据同步 & 读写分离
  2. 数据恢复(Point-in-Time Recovery)

    • 例如:昨晚有完整备份,今天上午有人误删了表;
    • 可以用“昨晚备份 + 今晨到现在的 binlog”把数据精确恢复到事故前一刻;
    • 这是很多企业“容灾恢复”方案里的关键一环。
  3. 数据同步到其他系统

    • 例如:把 MySQL 的变更实时同步到 ES、缓存系统、数据仓库等;
    • 许多 CDC(Change Data Capture)框架就是基于 binlog 做增量同步的。

三、总结一句话

你可以像视频里一样收口:

简单总结,binlog 通过复制、恢复、同步这三块能力,
保证了数据的高可用、不丢失、可追溯
是 MySQL 里非常核心的组件之一。


四、面试回答结构模板

可以按这个节奏讲:

  1. 先用“流水账本”的类比解释 binlog 是什么。
  2. 再分三点讲清用途:主从复制 / 数据恢复 / 跨系统同步。
  3. 最后一句总结它对高可用的价值。

这样就够清晰、有画面、有深度。


四、HashMap 七大“地狱级”问题:不背细节就等于“判死刑”

HashMap 在 Java 基础面试里的地位:
“如果这块答得不行,面试官心里基本已经给你打了叉。”
下面这 7 个问题是近几年最常被拷打的点。

一、为什么链表转红黑树阈值是 >8,转回是 ≤6?

  • 如果“树化阈值”和“反树化阈值”都设为 8:
    • 在插入/删除边界附近,长度会在 8 和 9 之间反复跳动;
    • 会导致红黑树 ↔ 链表频繁互转,性能波动大。
  • 采用 8 / 6 这样的带滞后区间的双阈值
    • 给数据结构一个“缓冲带”;
    • 避免在临界值附近来回抖动,提升整体稳定性。

二、数组什么时候初始化?JDK 7 和 8 有什么区别?

  • JDK 7new HashMap() 时就直接分配数组。
  • JDK 8+:数组初始化改为延迟到第一次 put 时才真正分配。
  • 设计思路:延迟初始化,按需分配资源,避免创建后长时间不用却占空间。

三、为什么默认负载因子是 0.75?

  • 本质是在空间利用率和冲突概率之间找平衡:
    • 负载因子太高 → 冲突变多,链表/树变长,查询退化。
    • 负载因子太低 → 空间巨大浪费。
  • 0.75 是基于:
    • 哈希分布特性(泊松分布)
    • 大量工程实践
      得出的统计意义上的经验最优点

四、提前知道要插 1 亿个元素,怎么优化?

关键是:一次性配够容量,避免频繁扩容迁移

  • 默认容量 16、负载因子 0.75:
    • 1 亿元素会触发多次扩容,每次要搬迁所有元素,非常耗性能。
  • 正确做法:
    • 期望容量 ≈ 期望元素数 / 负载因子 + 1
    • 100,000,000 / 0.75 + 1 ≈ 133,333,334
    • 再向上取 2 的幂,得一个合适的初始容量(例如 2^27)。
  • 细节优化:
    • 优先用 putAll 批量插入,减少循环中重复检查。
    • 为 key 实现一个分布足够均匀的 hashCode,避免严重冲突。

五、为什么容量必须是 2 的幂?非 2 的幂有什么问题?

  • HashMap 用的是 (n - 1) & hash 代替 hash % n
    • 位运算比取模快。
    • 容量是 2 的幂时,可以让高位参与运算,使分布更均匀。
  • 如果不是 2 的幂:
    • 某些位永远没被用到,哈希桶分布不均。
    • 冲突增多、局部桶过热,性能和空间利用率变差。

六、key 为 null 时怎么存?为什么只允许一个 null key?

  • HashMap null key 做了特殊逻辑
    • 存储时:直接放在 索引 0 的桶里(不算 hash,不调用 hashCode)。
    • 查询时:遇到 null key,直接去桶 0 里遍历链表/红黑树,用 equals 再判断。
  • 为什么只能有一个 null key?
    • HashMap 语义是“一个 key 对应一个 value”,null 自然也只能出现一次;
    • 多次 put(null, v),后面的值覆盖前面的值,跟普通 key 一样。

七、为什么不建议可变对象做 key?会出什么问题?

  • key 定位依赖:hashCode + equals
  • 这要求:key 的 hash 值在存储期间不能变
  • 如果用可变对象(例如 Student,可以 set age, score),中途属性变化导致 hashCode 改变:
    • 它原来所在桶的位置不再正确;
    • 再查找时会去错误的桶,导致“明明 put 过,却 get 不出来”。
  • 补救方式(不推荐做法,只是原理):
    • 通过合理实现 hashCode/equals,确保变化策略可控,或者保证作为 key 时不再修改内部状态。

八、面试答题建议

  • 不要一上来就背源码细节,先列 7 个问题的标题
    • 阈值 8 / 6
    • 延迟初始化
    • 负载因子 0.75
    • 大量元素时的初始容量
    • 容量必须是 2 的幂
    • null key 的处理
    • 可变对象做 key 的问题
  • 然后挑面试官感兴趣的点“展开一两个”,展示你既理解设计动机,也知道工程含义。

总结

  • 已按你“线程协同”那篇的结构和深度,分别整理了:
    • MySQL 联合索引 + 不等号 + 范围查询 + 覆盖索引
    • 多线程有序执行的 9 大方案
    • MySQL binlog 的作用和类比
    • HashMap 7 个地狱级问题
  • 都偏向“面试场景可直接输出”的稿子,你可以按平台(小红书/公众号)再微调长度和口语化程度。

如果你愿意,下一步我可以:

  • 帮你把每篇再压缩一版“30 秒口播提纲”;
  • 或者针对其中一篇,补一套“面试官↔候选人”的对话脚本。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值