在这里插入图片描述
分库分表的核心逻辑是**“路由”
当你按照 user_id 进行 Hash 取模(比如 user_id % 100)来决定数据落在哪张表时,user_id 就是你的
分片键 (Sharding Key)**。

如果用户的 SQL 是 SELECT * FROM order WHERE user_id = 123,中间件(如 ShardingSphere)会瞬间算出这条数据在 order_23 表,直接把 SQL 路由过去。极其丝滑。

但如果是下面这两种情况呢?


💣 痛点一:非分片键查询(迷失在 100 张表中的 SQL)

场景:用户想通过订单号查订单 WHERE order_sn = 'SN999';或者商家想查自己店铺的订单 WHERE merchant_id = 888
死局:因为你的 SQL 里没有带 user_id,分库分表中间件根本不知道这条数据在哪个库哪张表!它只能做一个极其愚蠢且致命的操作:全路由 (Broadcast)
它会把这条 SQL 同时发给 100 张表,等 100 个结果返回后在内存里合并。瞬间榨干数据库的连接池,系统直接挂掉。

大厂解法 1:ID 基因法 (The Gene Approach)

这是阿里、京东解决 order_sn 查询的最经典方案。
既然根据 order_sn 算不出在哪张表,那我们就在生成 order_sn 的时候,user_id 的基因嵌进去!

  1. 假设 user_id = 10059,取它的后 4 位 0059 作为路由基因。
  2. 在生成订单号时,用分布式 ID 算法(如雪花算法)生成一串前缀,然后强行把 0059 拼在订单号的最后面,变成:SN_12345678_0059
  3. 当用户用订单号查询时,中间件截取订单号的后 4 位 0059,立刻就能算出它在第 59 张表!完美避免全表路由。

大厂解法 2:异构索引表与 CQRS 架构

如果是商家要查自己的订单(带 merchant_id),基因法就行不通了,因为一条订单不能同时携带买家和卖家的基因。
这时候只能用空间换时间:

  1. 买家视角:依然按照 user_id 分库分表,保证买家高并发下单极速响应。
  2. 卖家视角/复杂后台查询:引入阿里 Canal 监听 MySQL 的 Binlog。一旦有订单写入,Canal 立刻将数据同步到 Elasticsearch (ES) 搜索引擎中。
  3. 卖家查询、后台产品经理查各种复杂条件组合,全部去查 ES,绝对不碰 MySQL!

🌊 痛点二:跨库分页的 OOM 惨案(内存杀手)

场景:后台管理员要查看全站的所有订单,按时间倒序排列,并且点击了第 1000 页。
SQLSELECT * FROM order ORDER BY create_time DESC LIMIT 10000, 10

死局
在单表里,这就是个普通的深分页。
但在 100 张分表里,这就是一场内存大爆炸
为了保证全局排序的准确性,中间件不能只去每张表拿 10 条数据。它必须去每一张表都执行 LIMIT 0, 10010
也就是说,它会从 100 张表里,总共捞出 100 万零 1000 条 数据,全部塞进中间件(应用服务器)的内存里!
然后在这个应用的内存中对这 100 万条数据进行二次大排序,最后截取前 10 条扔给前端,把剩下的 99 万多条直接丢弃。

结果:你的应用服务器瞬间 OOM (Out Of Memory) 宕机,CPU 飙升到 100%。

大厂解法 1:业务妥协(游标分页法 Cursor)

这是最高效的解法,但需要说服产品经理。
彻底废弃“跳页”功能,不允许输入页码,只能点“下一页”。

  1. 第一次查询(第 1 页):SELECT * FROM order ORDER BY id DESC LIMIT 10。记录下返回的最后一条记录的 ID,比如 last_id = 9990
  2. 点击下一页(第 2 页):将上一页的 last_id 带入查询条件:SELECT * FROM order WHERE id < 9990 ORDER BY id DESC LIMIT 10
  3. 优势:因为加上了 WHERE id < 9990 的条件,无论翻到第几万页,数据库每次都只需要扫描 10 条数据。在分库分表中,中间件也只需要去每张表拿 10 条,完全没有内存压力。这就是各大 App 信息流(如抖音、朋友圈)下拉刷新的底层逻辑。

大厂解法 2:二次查询法 (极其硬核)

如果非要跳页,又不想 OOM,可以采用复杂的“二次查询”。

  1. 第一次查询:把 LIMIT 10000, 10 改写为 LIMIT 100, 10 (也就是均摊到每个库)。从每个库只拿出极少量的数据。
  2. 找到这批数据中的全局最小值(Time_Min)。
  3. 第二次查询:把 Time_Min 作为一个 BETWEEN 条件,再次去各个库进行精准拉取,从而极大地缩小数据集。
    (注:这种方案工程实现极其复杂,目前业界更倾向于直接把数据同步到 ES 去做深分页,或者直接限制深分页最大深度为 100 页)

🎯 总结:没有银弹,只有取舍

分库分表是关系型数据库被逼到悬崖边上的无奈之举。它用极度复杂的“路由与聚合”逻辑,换取了存储容量和并发写入的上限。

  • 非分片键查询:用“基因法”把路由信息刻在 ID 里,或者用 Binlog 异构同步到 Elasticsearch。
  • 跨库深分页:坚决对产品经理说不,改用“游标法 (Cursor)”,或者交由专业的搜索引擎来处理。
Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐