写在开头昨天在技术群里看到个老哥心态崩了。 说是去字节二面面试官抛了个场景题“我有 10 亿个用户手机号请你设计一个系统支持毫秒级查询手机尾号后 4 位。”老哥一听这也太简单了张口就来“在 MySQL 建个索引或者用LIKE %1234呗。”面试官当场就笑了“10 亿数据你用 LIKE这是要全表扫描把数据库炸穿吗就算你建了普通索引B 树的最左前缀原则也不认后缀啊。再想想”老哥汗流浃背支支吾吾半天“那……上 ES”面试官摇头“为了查个尾号你让我搭一套 ES 集群这硬件成本、维护成本还有数据同步的延迟怎么算杀鸡用牛刀”这道题其实是架构设计里典型的“照妖镜”。它考的根本不是 SQL 语法而是你对“异构索引”、“分布式路由”以及“存储成本”的权衡。今天咱们就扒开这 10 亿数据的外衣看看怎么从架构层面把性能榨干。一、 找死流LIKE % / 暴力索引很多初学者第一反应就是SELECT * FROM users WHERE phone LIKE %1234。结局DBA 提刀赶来你的工位可能保不住了。为什么MySQL 的 B 树索引是严谨的“从左到右”排序。你查后缀尾号完全违背了“最左前缀原则”。这时候索引就是个摆设数据库被迫全表扫描。 10 亿数据哪怕全是 SSD扫一遍也得几十分钟。毫秒级梦里啥都有。二、 入门流反转大法好稍微有点经验的兄弟会想到“既然索引不支持从右往左查那我就把数据倒过来存。”骚操作 存手机号时顺手存一个反转字符串。比如13800138000存成00083100831。 查尾号1234就变成了查reversed_phone LIKE 4321%。效果 这就完美符合了“最左前缀”索引生效了查询确实快了。但在 10 亿数据面前这招还是不够看单表 10 亿光索引文件都得几十上百 G。B 树层高一变高IO 还是瓶颈。而且你这一台数据库能扛多少并发三、 进阶流分库分表 异构索引P7 必杀技到了这个量级分库分表是必须的。但这里有个巨坑。通常我们分库分表是按User_ID分的。 如果你要查“尾号 1234”你根本不知道这些人在哪个库里。 这就导致了分布式系统最忌讳的“广播查询Scatter-Gather”—— 你得向所有分片库同时发起查询然后聚合结果。这会让数据库连接池瞬间爆炸。怎么破建立“异构索引表”别动主表主表还是按 ID 分我们单独建一套映射表只存phone_suffix和user_id。1. 怎么分片重点千万别傻乎乎地用 Hash。 手机尾号0000-9999本身就是数字分布极度均匀 建议直接切1000 张表。路由算法table_index suffix % 1000。效果炸裂 你要查尾号12341234 % 1000 234。 请求直接打到第 234 号表。精准定位根本不需要去骚扰其他 999 张表。哪怕数据量再大单表也就 100 万行MySQL 跑起来跟玩一样。2. 隐藏的雷区很多人挂在这面试官这时候通常会冷笑一下“查尾号 1234会返回多少条数据”咱们算笔账10 亿用户尾号只有 1 万种组合。10亿 / 1万 10万。 平均每个尾号对应10 万个用户如果你代码里写个SELECT *一次返回 10 万条数据你的应用服务器内存直接OOM带宽瞬间打满。解法必须强制分页Limit。 告诉面试官“业务上我们只展示‘最新注册’的 20 个用户SQL 强制加上ORDER BY user_id DESC LIMIT 20。想看更多的不支持或者加钱上分析型数据库。”四、 兜底流Redis 怎么用这时候肯定有人说“加个 Redis 缓存啊”避坑指南 千万别提布隆过滤器 尾号只有 0000-9999这 1 万个尾号肯定都存在。布隆过滤器是防空的这里全是满的用了个寂寞。正确的 Redis 姿势 只缓存“首页”。 把尾号8888这种热点数据的前 50 条user_id扔进 RedisList 或 ZSet。 99% 的用户就是看个热闹Redis 挡住这部分流量就够了。真正想深度翻页的让他去查异构表反正量也不大。总结建议背诵下次面试再问这个直接用这就话降维打击“面试官这个问题的本质是海量数据的非分片键查询。我不会搞昂贵的 ES而是采用‘异构索引表 覆盖索引’的方案。 建立一张独立的索引表利用手机尾号天然的离散性直接取模路由到 1000 张分表中避免了全库广播扫描。 同时为了防止单次查询数据量过大打爆内存我会严格限制分页查询。 对于热点尾号利用 Redis 缓存首页数据进行兜底。 这是一套成本最低、性能最高、且完全可落地的架构方案。”写在最后所谓的架构设计从来不是堆砌组件。 能用 MySQL 解决的绝不上 ES能用取模解决的绝不上 Hash。把复杂留给自己把简单留给机器这才是高手的境界。兄弟们如果产品经理非要让你支持“模糊搜索任意中间 4 位”比如%1234%除了提桶跑路你还有什么招 评论区聊聊。