一、非分片键查询说明
如果查询时,查询条件带有分片键字段,那么查询时直接定位到一张表/一个库,查询性能比较高
但不可避免的,会有如下场景:查询条件中没有分片键,而是其他字段,此时叫做非分片键查询
例如上述电商场景的订单数据,如果我要根据订单号查询呢?这也是一个比较高频的业务场景
方案选择:
- 方案一:扫描所有表/库,查询出数据,再聚合,性能低
- 方案二:基因法,性能高
二、基因法
基因法是一种在 ID 设计阶段 就考虑到分片的思路。
它利用数学上的一个规律:
当 B 是 2 的 n 次幂时,A % B 等价于 A 的二进制表示的后 n 位。
例如:
9 % 4 = 1 → 1001 的后两位是 01
10 % 4 = 2 → 1010 的后两位是 10
15 % 8 = 7 → 1111 的后三位是 111
因此,如果系统中表的数量为 2ⁿ 张,我们只要在生成 ID 时,
将“分片键取模结果”(也就是那后 n 位)嵌入到 ID 的末尾,
就能保证根据该 ID 再次取模时,能路由到与分片键一致的表。
这就是所谓的“在 ID 中注入分片基因”。
三. 我们项目中的场景与问题分析
在本项目中,我们的核心表是 message 表,
它是一个高频写入和查询的表,用于存储会议消息。
我们在设计时选择:
-
分片键(sharding key):
meetingId -
分片算法:
hash(meetingId) % 32 + 1 -
主键(messageId):使用雪花算法(Snowflake)生成的全局唯一 ID
这意味着,系统在插入消息时,只要知道 meetingId,
就能通过哈希算法确定要写入哪张消息表。
四. 为什么我们的项目不存在非分片键查询问题(如何规避)
理论上讲,如果用户只携带了 messageId 而没有 meetingId,
那就属于“非分片键查询”场景。
但在我们的系统设计中,这种情况被巧妙规避了。
原因如下 👇
(1)messageId 和 meetingId 是强关联的
message 表中同时保存了 messageId 和 meetingId 两个字段。
即使只拿到 messageId,我们也可以通过一次索引表查询反推出 meetingId:
SELECT meeting_id
FROM message_global_index
WHERE message_id = ?;
随后再利用 hash(meetingId) % 32 即可定位到对应的分表。
✅ 这属于“可反查”的场景,而不是严格意义上的“非分片键查询”。
(2)系统中所有与消息相关的查询均带有 meetingId
在业务逻辑上,任何一条消息都必须属于某个会议。
例如以下几种查询方式:
-- 查询会议下的所有消息
SELECT * FROM message_05 WHERE meeting_id = ?;
-- 查询会议下某一条特定消息
SELECT * FROM message_05 WHERE meeting_id = ? AND message_id = ?;
在这种严格的业务约束下,
系统天然避免了“只靠 messageId 查询”的情况。
(3)因此messageId 使用雪花算法,不承担分片职责
雪花算法生成的 ID 仅仅保证全局唯一性与时间有序性,
并不参与分片逻辑,也不会与 meetingId 冲突。
分片的全部逻辑由 meetingId 控制,雪花 ID 仅仅作为主键使用。
五. 对比总结
| 场景 | 普通系统 | 我们的项目 |
|---|---|---|
| 分片键缺失 | 会触发全表扫描或需要基因法 | 可通过索引反查 meetingId |
| 主键生成方式 | 基因嵌入或随机 UUID | 雪花算法(与分片逻辑解耦) |
| 查询设计 | 部分接口无分片键 | 所有查询都基于 meetingId |
| 性能影响 | 存在“非分片键查询”性能瓶颈 | ✅ 已彻底规避此问题 |
小结
非分片键查询是分库分表系统中最常见、最棘手的问题之一。
然而在本项目中,通过以下两点设计,我们成功规避了该问题:
-
将
meetingId作为核心分片键,所有数据的分布完全由它决定。 -
messageId与meetingId强关联,即使只有 messageId 也能快速反查 meetingId。
因此,项目在分库分表架构下,仍然能保证:
查询高效、路由明确、无全表扫描风险。

4208

被折叠的 条评论
为什么被折叠?



