业务代码里为什么禁止使用 JOIN ?
date
Mar 15, 2023
slug
why-ban-join-operate-in-business-service-code
status
Published
tags
Code
summary
How? And Why?
type
Post
Created Time
Oct 28, 2023 01:45 PM
Updated Time
Oct 30, 2024 07:10 AM
AI summary
Status
早些时候,在业务开发中,曾被有经验的开发者忠告过“业务代码里不要使用 JOIN”,一些公司甚至把这一条写在代码规范里,当时也没太在意,最近开发 ToB 的业务中,发现有一个场景,如果使用 JOIN,可以节省大量的代码,于是便好奇,为什么不能在业务代码里使用 JOIN 呢?这个 JOIN 是什么牛鬼蛇神?它既然存在,就一定有存在的理由,在什么场景下可以使用 JOIN 呢?
Join 是什么?
菜鸟教程里将 JOIN 按照功能,划分为以下三种:
- INNER JOIN(内连接,或等值连接):获取两个表中字段匹配关系的记录。
- LEFT JOIN(左连接):获取左表所有记录,即使右表没有对应匹配的记录。
- RIGHT JOIN(右连接): 与 LEFT JOIN 相反,用于获取右表所有记录,即使左表没有对应匹配的记录。
上面这张著名的图介绍了 SQL JOIN 相关的 7 种用法。当我看了阮老师的简单解释后,心满意足的发现,自己其实想知道的是:JOIN 为什么性能差呢?
上面这张图介绍了 SQL 的执行顺序,从这个顺序中我们可以发现,所有的查询语句都是从 FROM 开始执行的。在实际执行过程中,每个步骤都会为下一个步骤生成一个虚拟表,这个虚拟表将作为下一个执行步骤的输入。
在执行 JOIN 语句的时候,会依次将两个表中的数据读进一个内存块中,以 MySQL 的 InnoDB 引擎为例,当执行下面的语句时:
会得到如下结果:
join_buffer_size
的大小决定了这个内存块一次最多能存储多少内容,当加载的数据大于这个值的时候,就会使用数据交换的方式执行 JOIN,可想而知,性能一定会大打折扣。另外,大部分数据库中的数据最终都是保存在硬盘上,并且以文件的形式进行存储的,以 InnoDB 引擎为例,InnoDB 以页为基本的 IO 单位,每页的大小固定为 16KB,这意味着要连接多少个表,就要读多少个文件,虽然可以利用索引,但免不了频繁的移动硬盘的磁头,相应的,性能也会大打折扣。
这么说,这个 JOIN 当真是碰不得吗?其实不然。
在什么场景下可以用 JOIN?
上面分析表明,JOIN 操作会对性能产生影响,主要有两个原因:
join_buffer_size
;
- 连表导致的磁头频繁移动;
也就是说,如果能避开这两点,使用 JOIN 就不会对性能有太大的影响(连表还会创建临时表,创建和销毁临时表也会影响性能),那么有没有办法避开上面这两点呢?答案是,没有,但可以尽量减少,比如,小表驱动大表,且通过索引字段进行关联。
但业务表中的数据往往是会不断增加的,现在的小表未来可能会变成大表,最好还是不要在业务中使用 JOIN。
JOIN 最佳的使用方式是:在一主多从的架构模型中,只去查询特定的从库,业务场景中允许一定程度的更新延迟,或者想办法模糊化更新延迟的问题。
在业务代码里如何使用 JOIN?
上面的内容再三强调——不要再业务代码里使用 JOIN,但有些场景确实需要使用 JOIN 怎么办?我的答案是,在应用中自己实现类似 JOIN 的方案,在应用层做关联。
比如,user 表和 order 表需要做关联,可以先找到 ID 集,通过 IN 的方式查询到所需数据,最后在应用层做整合。
这种方式的坏处是增加了单个查询的次数,且在关联的表较多时,维护成本也会相应提高,不如 JOIN 来的直接,但好处是将查询分解后,执行单个查询能够减少锁的竞争,且具有更高查询性能和可拓展性,最重要的,极大的降低了数据库的压力。总结来看,就是用缓存去降低内存的压力。
知其然,然后知其所以然,才能更好的将工具为我所用。JOIN 是一把双刃剑,知其利弊,权衡得失,把控局势,即使在业务场景里,其实也是可以使用 JOIN 的,一个真正的极客,从来都不会给自己设限!