分库分表学习笔记:表关联
发布时间:2026/10/14 10:51:27 锦皓数字建站

分库分表学习笔记表关联JOIN怎么处理一、普通数据库中表关联是怎么做的假设电商系统有两张表用户表和订单表。用户表useridname1张三2李四3王五订单表ordersiduser_idamount101120010213001032500其中orders.user_id对应user.id。普通数据库中可以直接执行SELECTo.idASorder_id,u.name,o.amountFROMorders oJOINuseruONo.user_idu.id;数据库通过关联条件匹配两张表的数据返回order_idnameamount101张三200102张三300103李四500在普通数据库中只要两张表在同一个数据库中数据库引擎通常就可以直接完成 JOIN。但是分库分表以后情况可能完全不同。二、分库分表后为什么 JOIN 会变复杂假设订单表按照user_id分成 4 张表orders_0user_id % 4 0 orders_1user_id % 4 1 orders_2user_id % 4 2 orders_3user_id % 4 3用户表也按照id分成 4 张表user_0id % 4 0 user_1id % 4 1 user_2id % 4 2 user_3id % 4 3由于订单表使用user_id分片而用户表使用id分片并且两者采用相同的取模规则因此用户与其订单会落在相同编号的分片中。例如user_id 1 user_1 └── 张三 orders_1 ├── 订单 101 └── 订单 102此时可以在对应分片中完成 JOIN。但假设两张表采用不同的分片规则用户表user.id % 4 订单表order.id % 4那么用户和订单可能落在不同的分片中。例如用户表 user_1 └── 用户 1张三 订单表 orders_1 └── 订单 101user_id 1如果订单表实际按order.id分片那么订单 101 会进入orders_1但其他订单可能分散到其他表。查询某个用户的所有订单时就可能需要跨分片关联。核心问题普通 JOIN 只需要考虑两张表的关联条件分库分表后的 JOIN 还必须考虑数据实际存放在哪里。三、方案一关联表使用相同的分片键和分片规则这是最值得优先考虑的方案通常称为数据共置Co-location。1. 怎么设计假设用户和订单之间是一对多关系一个用户 ├── 订单 A ├── 订单 B └── 订单 C让用户表和订单表采用相同的分片规则用户表user.id % 4 订单表orders.user_id % 4这样同一个用户及其订单会存储在对应的分片中。例如分片 0 ├── user_0 └── orders_0 分片 1 ├── user_1 └── orders_1 分片 2 ├── user_2 └── orders_2 分片 3 ├── user_3 └── orders_3查询用户及其订单时SELECTu.name,o.idASorder_id,o.amountFROMuseruJOINorders oONu.ido.user_idWHEREu.id1;分片中间件可以根据用户 ID 定位到分片 1再在该分片内执行 JOIN。2. 为什么这种方案性能好因为数据库不需要跨多个数据库查询数据也不需要把大量数据传输到应用程序后再进行关联。执行过程大致如下接收 SQL ↓ 根据 user.id 1 计算分片 ↓ 定位到分片 1 ↓ 在分片 1 内执行 JOIN ↓ 返回结果这种方式的优点可以保留传统 SQL JOIN。数据传输量相对较小。不需要跨库协调关联过程。适合高频的用户订单查询。3. 有什么限制前提是两张表必须具有兼容的分片规则并且数据确实共置。如果订单按照order.id分片而用户按照user.id分片就不能直接套用这个方案。此外如果需要按照用户 ID 查询订单使用user_id分片通常很合适但如果经常按照订单 ID 查询订单就还要考虑如何通过订单 ID 快速定位分片。因此分片键不仅影响数据分布也会影响 JOIN 的设计。四、方案二广播表Broadcast Table如果一张表很小而且数据量比较稳定可以将它的完整数据复制到每个分片。例如商品分类表category分类数据只有几十条idname1手机2电脑3家电可以让每个数据库分片都保存一份完整的分类表分片 0 ├── orders_0 └── category 分片 1 ├── orders_1 └── category 分片 2 ├── orders_2 └── category 分片 3 ├── orders_3 └── category这样每张订单表都可以直接关联本地的category表。例如SELECTo.id,o.amount,c.nameAScategory_nameFROMorders oJOINcategory cONo.category_idc.idWHEREo.user_id10086;如果订单路由到分片 2就可以直接关联分片 2 中的category表。为什么不把所有表都复制一份因为广播表的代价是每个分片都保存完整数据。如果订单表有 10 亿条记录把它复制到每个数据库会产生巨大的存储和维护成本。但是如果表只有几十条或几百条数据复制成本就可能很低。适合什么场景商品分类表。国家、地区字典表。订单状态字典表。其他数据量较小、更新频率较低的公共表。需要注意广播表更新时要保证各个分片的数据最终一致。不能因为复制了多份就假设所有副本始终同时更新成功。五、方案三应用层关联Application-side JOIN如果两张表的数据分布不同而且无法调整分片规则可以由应用程序分别查询再根据关联字段合并结果。例如需要查询用户 1 的订单和用户信息。第一步查询订单。SELECT*FROMordersWHEREuser_id1;得到订单 101user_id 1amount 200 订单 102user_id 1amount 300第二步根据user_id查询用户。SELECT*FROMuserWHEREid1;得到用户 1name 张三第三步在 Java 中组合数据。UseruseruserMapper.selectById(1L);ListOrderordersorderMapper.selectByUserId(1L);ListOrderDTOresultorders.stream().map(order-{OrderDTOdtonewOrderDTO();dto.setOrderId(order.getId());dto.setUserName(user.getName());dto.setAmount(order.getAmount());returndto;}).toList();这里假设OrderDTO包含orderId、userName和amount字段。这种方案有什么优缺点优点不要求两张表采用相同的分片规则。业务逻辑清晰。可以控制查询顺序和结果合并方式。缺点应用程序需要承担数据合并工作。如果每条订单都单独查询用户会产生大量数据库请求。多次查询之间可能存在数据变化不能天然保证一致性。例如查询 1000 条订单后再逐条查询用户信息就可能产生 1000 次额外请求。这就是常见的 N1 查询问题。优化方式是批量查询SELECT*FROMuserWHEREidIN(1,2,3,4,5);然后在 Java 中构建 MapMapLong,UseruserMapusers.stream().collect(Collectors.toMap(User::getId,Function.identity()));之后根据order.getUserId()从 Map 中获取用户信息避免逐条访问数据库。注意如果user表本身也进行了分库分表批量查询仍然需要根据用户 ID 将请求路由到相应分片并合并查询结果。六、方案四冗余字段减少 JOIN有些场景可以直接将关联表中的部分字段复制到主表。例如订单表原本只有orders ├── id ├── user_id └── amount每次查询订单列表时都需要关联用户表才能获得用户名SELECTo.id,u.name,o.amountFROMorders oJOINuseruONo.user_idu.id;可以考虑在订单表中增加user_nameorders ├── id ├── user_id ├── user_name └── amount创建订单时把用户 ID 和用户名一起保存。查询时SELECTid,user_name,amountFROMordersWHEREuser_id10086;这样就不必每次查询都 JOIN 用户表。为什么这种方式适合分库分表因为订单查询可以完全在订单所在分片中完成不需要跨库关联用户表。对于订单详情中的用户姓名、商品名称等展示字段如果业务允许使用创建订单时的快照这种方式尤其常见。需要付出什么代价如果用户修改了姓名历史订单中的user_name不会自动改变。因此必须先明确字段语义历史快照保留订单创建时的姓名不随用户资料变化。实时资料希望每次展示最新姓名则需要额外的数据同步或查询机制。冗余字段并不一定是数据设计错误。它是一种通过增加存储空间和维护成本换取查询效率的设计方式。但是不要不加区分地复制所有字段。应该只冗余高频查询、能够明确更新语义的字段。七、方案五跨分片 JOIN如果两张表采用不同的分片规则且确实需要一次性完成关联就可能遇到跨分片 JOIN。例如订单表按照 order_id 分片 用户表按照 user_id 分片执行SELECTo.id,u.name,o.amountFROMorders oJOINuseruONo.user_idu.idWHEREo.create_time2026-10-01;由于查询条件没有提供可以直接定位所有目标数据的分片键订单和用户数据可能分别位于多个分片。这时分库分表框架可能需要向多个订单分片发送查询。向多个用户分片发送查询。获取各分片的数据。根据关联条件进行数据匹配。合并结果并返回。实际执行方式取决于中间件、SQL 结构和分片规则不是所有框架都能对任意跨分片 JOIN 提供高效支持。为什么这种方式可能很慢假设有 8 个订单分片和 8 个用户分片。如果不能根据关联条件有效缩小查询范围就可能需要访问多个分片产生大量网络传输和结果合并工作。此外跨分片 JOIN 还可能涉及排序、聚合和分页成本会进一步增加。应该怎么处理按优先级考虑调整分片设计让经常关联的数据共置。将小型公共表设计为广播表。对展示型查询使用冗余字段或应用层关联。对复杂后台检索建立独立查询模型。确实需要跨分片 JOIN 时再评估中间件的支持能力和压测结果。跨分片 JOIN 并非绝对不能使用但不应该在不了解执行计划和数据规模的情况下把它当作普通数据库 JOIN 使用。八、实际项目应该怎么选业务场景优先考虑的方案原因查询某个用户的订单及用户信息相同分片规则数据共置可以在本地 JOIN订单关联商品分类广播表分类数据量小复制成本低两张表分片规则不同查询少量记录应用层关联可以控制查询和合并过程订单列表经常展示用户名、商品名称冗余字段减少高频查询中的 JOIN后台需要跨全平台复杂检索独立查询模型或搜索引擎避免频繁扫描全部分片确实需要多个大表跨分片关联重新评估数据模型和分片规则避免大量网络传输和结果合并九、最后总结分库分表后表关联主要有以下几种处理方式相同分片键和分片规则让关联数据存储在相同分片优先考虑。广播表将小型公共表复制到各个分片。应用层关联分别查询数据在 Java 中完成合并。冗余字段将高频展示字段复制到主表减少 JOIN。跨分片 JOIN由中间件或查询层处理但要特别关注性能和兼容性。最重要的设计原则是分库分表之前不仅要考虑一张表如何拆分还要考虑拆分之后哪些表需要关联、关联条件是什么以及数据是否能够在同一个分片中完成关联。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。