知识卡片
水平分表后join、count()与order by操作的应对方式
内容
除了[[水平分表的路由算法:范围路由、Hash路由与配置路由的权衡]],水平分表还会让几个原本在单表上很简单的操作变得复杂。join操作:数据分散到多个子表后,如果要和其他表联合查询,只能在业务代码或数据库中间件里对每个子表分别做查询、再把结果合并,不能再依赖数据库原生的一次join完成。count()操作:物理上数据分散在多张表里,但业务逻辑上经常还是要把它们当一张表处理(比如做分页要拿到总记录数),分表前一次count()就能搞定的事,分表后有两种常见处理方式——”count()相加”是对每个子表分别count()再把结果加起来,实现简单但性能较差(比如分成20张表,串行执行20次count(*)可能要几秒才能拿到结果);”记录数表”是新建一张专门记录每个子表行数的表,每次子表插入或删除成功后同步更新这张记录数表,读取时只需一次简单查询,性能明显优于count()相加,但代价是复杂度显著上升——任何一处子表操作忘了同步更新记录数表就会导致数据不一致,而且针对子表的操作和针对记录数表的操作天然无法放进同一个事务,异常情况下完全可能出现”子表操作成功但记录数表更新失败”这种不一致;对不要求记录数实时精确的业务,还可以退而求其次,用后台定时任务通过”count()相加”的方式周期性刷新记录数表,相当于把两种方式结合起来,用牺牲实时性换取写压力和一致性风险的降低。order by操作:数据分散在多个子表后,排序天然无法在数据库层面一次性完成,只能由业务代码或中间件分别查询各子表数据,再把结果汇总后统一排序。分库分表的最终技术实现路径和读写分离一样,也是”程序代码封装”与”中间件封装”两条路线,但实现复杂度明显更高——读写分离只需要识别一条SQL是读还是写(判断SELECT/UPDATE/INSERT/DELETE关键字即可),分库分表除了判断操作类型,还要进一步识别SQL具体操作的是哪张表、有没有用到count这类聚合函数、有没有order by/group by,再针对不同情况分别处理(例如order by要先从各库分别查出数据,再对合并后的结果重新排序一次才能得到最终结果)。