虽然分步查询代码可复用性高、更利于后续的维护、可以更好的利用缓存,但是阿里规范禁止三张表 JOIN 的最迫不得已的原因为:

  • 去 IOE 行动后,MySQL 优化器和执行器很弱,多表连接性能不如 Oracle
  • 单表在 MySQL 中数据量太大,由于它的索引结构设计没有针对大表,所以查询性能会断崖式下滑
  • 不得不考虑分库分表 + 中间件的模型,但跨库 JOIN 的性能受网络 IO 瓶颈,除非业务能够很好的根据 sharding key 明确要 Join 的两个表在同一个物理库中
  • 如果将 Join 放到中间件去实现,由于中间件拿到数据 sharding 信息更难,成本肯定更大
  • 中间件一般对跨库 join 都支持不好。在分库分表中,要同步更新两个表,这两个表位于不同的物理库中,为了保证数据一致性,一种做法是通过分布式事务中间件将两个更新操作放到一个事务中,但这样的操作一般要加全局锁,性能很捉急,而有些业务能够容忍短暂的数据不一致,怎么做?让它们分别更新呗,但是会存在数据写失败的问题,那就起个定时任务,扫描下 A 表有没有失败的行,然后看看 B 表是不是也没写成功,然后对这两条关联记录做订正,这个时候同样没法用 join 去实现,只能将数据拉到 service 层应用自己来合并了

这些规则都是互联网开发团队总结出来的,适用于高并发、轻写重读、分布式、业务逻辑简单的情况,甚至对数据的一致性要求都不高,允许脏读(毕竟 web 是个非实时、无状态的东西)。

而对于很多低并发、频繁复杂数据写入、CPU 密集而非 IO 密集、主要业务逻辑通过数据库处理、甚至包含大量存储过程、对一致性与完整性要求很高的系统。比如金融、财务、企业应用之类,复杂 Join 也是不可避免的,不仅要写,还要写好,才能发挥数据库最大的功用。

在我的 Team 中,有很多 DB 同事,为 Java 开发写好了 SAP HANA 数据库的存储过程,进行了大量数据清洗,理应利用高效的内存数据库优势。此外,由于 SCI 系统的内存资源比较紧张,一个微服务在容器云上目前最多只能分配 10G,同时业务表数据量比较巨大,一个 version 几千万的数据是很正常的,如果分步查询,每个步骤的获取的结果都需要拿到 JVM 内存中与下一步的结果去做处理,会比较占用内存。因此很多报表的处理思路都是能在 SQL 中解决的就在 SQL 中解决,Java 代码中不做复杂的逻辑。