浅析MySQL多次查询和关联查询的效率问题
Posted longlongbug
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅析MySQL多次查询和关联查询的效率问题相关的知识,希望对你有一定的参考价值。
自高性能mysql:
一、MySQL多表关联查询效率高点还是多次单表查询效率高,为什么?
A、B两个表数据规模十几万,数据规模都不大,单机MySQL够用了,在单机的基础上要关联两表的数据。
先说一个极端情况,A、B两个表都没有索引,并且关联是笛卡尔积,那关联结果会爆炸式增长,可能到亿级别,这个时候网络IO成了瓶颈,这个时候两次十万行结果集的拉取可能远小于1次亿级别的结果集的拉取。
那么将关联合并拉到 service 层做更快。
但实际业务中一般不会有这么蠢的行为,一般关联会有连接条件,并且连接条件上会有索引,一般是有一个结果集比较小,拿到这个结果集去另一张表去关联出其它信息。
如果放到service层去做,最快的方式是,先查A表,得到一个小的结果集,一次 rpc,再根据结果集,拼凑出B表的查询条件,去B表查到一个结果集,再一次rpc,再把结果集拉回service层,再一次rpc,然后service层做合并。
3次 rpc,如果用数据库的 join,关联结果拉回来,一次 rpc,帮你省了两次 rpc,当然数据库上做关联更快,对应到数据库就是一次 blk nested loop join,这是业务常用情况。
但是确实大多数业务都会考虑把这种合并操作放到service层,我觉得有几方面考虑:
第一:单机数据库计算资源很贵,数据库同时要服务写和读,都需要消耗 CPU,为了能让数据库的吞吐变得更高,而业务又不在乎那几百微妙到毫秒级的延时差距,业务会把更多计算放到 service 层做,毕竟计算资源很好水平扩展,数据库很难啊,所以大多数业务会把纯计算操作放到service层做,而将数据库当成一种带事务能力的 kv 系统来使用,这是一种重业务,轻DB的架构思路 。
第二:很多复杂的业务可能会由于发展的历史原因,一般不会只用一种数据库,一般会在多个数据库上加一层中间件,多个数据库之间还能做毛的 join,自然业务会抽象出一个service层,降低对数据库的耦合。
第三:对于一些大型公司由于数据规模庞大,不得不对数据库进行分库分表,这个问题在《阿里为什么要禁用三表以上的join》上也有描述:对于分库分表的应用,使用 join 也受到了很多限制,除非业务能够很好的根据 sharding key 明确要 join 的两个表在同一个物理库中,而中间件一般对跨库 join 都支持不好。
举一个很常见的业务例子,在分库分表中,要同步更新两个表,这两个表位于不同的物理库中,为了保证数据一致性,一种做法是通过分布式事务中间件将两个更新操作放到一个事务中,但这样的操作一般要加全局锁,性能很捉急,而有些业务能够容忍短暂的数据不一致,怎么做?让它们分别更新呗,但是会存在数据写失败的问题,那就起个定时任务,扫描下A表有没有失败的行,然后看看B表是不是也没写成功,然后对这两条关联记录做订正,这个时候同样没法用join去实现,只能将数据拉到service层应用自己来合并了。
补充一下:使用join未必效率全低,曾遇到的一个慢sql调优,为方便简单写:
// 步骤1
select tableA.id as ids from tableA where age>20;
// 步骤二,使用上一步的查询结果:
select tableB.score from tableB where id in (ids);
// 这是一个很常见的查询,步骤一和步骤二,相当于
select tableB.score from tableB inner join tableA on tableA.id=tableB.id;
// 这个效率谁高,看具体情况了。最后测试结果是inner join的效率高。
为什么互联网公司不让使用join?
第一:不利于写操作。执行读操作时,会锁住被读的数据,阻塞其他业务对该部分数据的更新操作(U or D)。如果涉及多个聚合函数(缓存中没有max or min时),相当于同时锁住多张表,不能进行读写,直接影响其他业务,这影响了系统的整体性能;
第二,不利于维护。业务发生变动时,比如 join 中一张表改了,可能导致系统中原有的 sql 不可用,最终导致基于该 SQL 执行结果的上层显示失败(也意味着以往的开发工作已无效)。
如果使用步骤一和步骤二的方式,只需要修改其中一个步骤就行。实际工作中,也就是只需要修改其中的一个service(对应一张表)即可。
既影响性能,又不利于维护,所以我们尽量不要用 join。另外,阿里集团不让用三张表,是在OLTP场景中,不要一叶障目哦,如果是在复杂分析型OLAP的应用场景中,使用 join 还是非常合适的。
看实际场景:
1、OLTP 比较忌讳多个大表连表查询,尤其是在未经特别优化的情况下,适当反范式设计可以减少连接查询提高性能
2、OLAP 中大表多表连接查询是不可避免的,除了优化索引,最佳实践就是化整为零,分布计算,一次2-3张表查询结果放入临时表
二、《阿里巴巴JAVA开发手册》里面写超过三张表禁止join 这是为什么?
1、为什么做这种限制?
打个比方,如果我有无限的钱,我想买个豪华别墅,想买个跑车,想买个直升飞机,那我可以随便买随便造;但现实是我没钱,只能租房住,只能走路上下班。
如果数据库的性能无限强大,多个表的join肯定是需要的,尤其是复杂的分析型(OLAP)查询,甚至可能涉及10几个表的 join,但现实是大部分数据库的性能都太弱了,尤其是涉及到多表 join 的查询。
规范一看就是在使用MySQL时的限制(这种规范实际上是迫不得已的限制),做这个限制有两个原因:
一是优化器很弱,涉及多个表的查询,往往得不到很好的查询计划,这块比较复杂;
二是执行器很弱,只有nested loop join,block nested loop join和index nested loop join。
1)nested loop join 就是分别从两个表读一行数据进行两两对比,复杂度是n^2
2)block nested loop join 是分别从两个表读很多行数据,然后进行两两对比,复杂度也是n^2,只是少了些函数调用等overhead
3)index nested loop join 是从第一个表读一行,然后在第二个表的索引中查找这个数据,索引是B+树索引,复杂度可以近似认为是nlogn,比上面两个好很多,这就是要保证关联字段有索引的原因
4)如果有hash join,就不用做这种限制了,用第一个表(小表)建hash table,第二个表在hash table中查找匹配的项,复杂度是n。缺点是hash table占的内存可能会比较大,不过也有基于磁盘的hash join,实现起来比较复杂。
2、在这种限制下SQL怎么写?
可是我确实需要两个表里的数据链接在一起啊,我们可以做个冗余,建表的时候,就把这些列放在一个表里
比如:一开始有 student(id, name),class(id, description),student_class(student_id, class_id)三张表,这样是符合数据库范式的(第一范式,第二范式,第三范式,BC范式等),没有任何冗余,
但是马上就不符合“编程规范“了,那我们可以用一张大表代替它,student_class_full(student_id, class_id, name, description),这样name和description可能要被存储多份,但是由于不需要join了,查询的性能就可以提高很多了。
任何的规范都是在特定情况下的某种妥协,脱离了这个环境,就不一定成立了。
以上是关于浅析MySQL多次查询和关联查询的效率问题的主要内容,如果未能解决你的问题,请参考以下文章