目录
情况1:tb1.id 和 tb2.id 字段都均无索引,时间复杂度为 N + N*M
情况2:tb1.id 和 tb2.id 字段都均有索引,时间复杂度为最大为N + N * logM,最小为 logN + logN * logM
问:join底层是怎样实现的?
select * from tb1 join tb2 on tb1.id=tb2.id
以上面的 并表查询 举例,执行上面sql的流程如下:
- 扫描 tb1,将 tb1 中符合的所有数据,放入内存 join_buffer 中,由于是 select *,所以是将整行记录都放入内存中;
- 获取 tb1 中一行记录的 id 数据,去 tb2 中匹配;
- 重复步骤2,直至匹配完join_buffer中 tb1 的所有数据;
- 所有匹配得到的结果,合成结果集,返回给客户端;
- 其中 tb1 为驱动表,tb2为被驱动表;
以上流程的大概流程图如下:
时间复杂度分析:
假设 tb1 中有 N 条记录,tb2 中有 M 条记录
情况1:tb1.id 和 tb2.id 字段都均无索引,时间复杂度为 N + N*M
-
由于tb1.id没有索引,扫描 tb1 表时是全表扫描,时间复杂度是 N,join_buffer中也有 N 条记录;
-
之后,join_buffer 中的每条记录都要去 tb2 中匹配,由于没有tb2.id也没有索引,也需要全表扫描,N 条记录匹配,则需要 N * M 次扫描扫描, 时间复杂度为 N*M;
-
1,2步骤合起来,时间复杂度是 N + N * M,如果 N=10000,M=100000,则需要10亿次扫描,时间复杂度太高相当耗时。
情况2:tb1.id 和 tb2.id 字段都均有索引,时间复杂度为最大为N + N * logM,最小为 logN + logN * logM
-
由于 select * from tb1 join tb2 on tb1.id=tb2.id 没有过滤条件,所有不管 tb1.id 有没索引,都需要全表扫描,时间复杂度是 N,join_buffer中也有 N 条记录;
-
join_buffer 中的每条记录都要去 tb2 匹配时,tb2.id有索引,则是索引树查询,每条记录的匹配时间为树高logM,N 条记录的时间复杂度则为 N * logM;
-
将 1,2步骤合起来,时间复杂度为 N + N * logM,mysql 中,一般存储10亿条数据的树高顶多为4,如果 N=10000,M=100000,则需要扫描 10000 + 10000 * log100000 ≈ 10000+10000*4 = 50000,扫描次数相对情况1少了非常多,由此可以看出索引在对性能优化上的巨大优势;
-
另外由时间复杂度 N + N * logM 可知,扫描行数小的表,简称小表,作为驱动表,优化效果更明显;
-
如果将 select * from tb1 join tb2 on tb1.id=tb2.id 改成 select * from tb1 join tb2 on tb1.id=tb2.id where tb1.id < 1000,则搜索tb1时可以采用树查询,时间复杂度为 logN,那么最终时间复杂度则为 logN + logN * logM,性能更一步优化;
结论:
综合情况1和情况2可知,如果并表查询的字段,有的不存在索引,则不推荐用并表查询,可以考虑分开表查询,拿到数据后再在程序里面做额外的处理;
如果并表查询的字段,均存在索引,可以考虑用并表查询。不过个人推荐还是不管什么情况都不要用并表查询,都采用分开查询后再在程序单独做处理的方式处理数据,让mysql尽量做更少的事,减少mysql负担。因为一般mysql的性能没业务层好拓展,性能比不上业务层的,将mysql做的事分担给业务层,能提高整体服务的吞吃量。