MySQL常见问题之SQL查询慢
可能是经常处理业务,最近总是听到开发的同学说SQL的查询慢。然后问我为什么,让我在数据库层面找原因。这样的需求接的多了,对于这类需求,我已经有了一套比较官方的回答思路,我来说,大家看,看看还有什么没有考虑到的地方,欢迎指正。
首先,当有业务方对我说SQL查询慢的时候,一般我会先问几个问题:
1、这个SQL是偶尔比较慢还是一直这么慢?
如果是偶尔比较慢,那大概率说明不是SQL层面的问题,应该是在某个时间点遇到了数据库的其他动作,导致产生了影响,例如:
第一、该条语句要扫描的表被加锁了,所以导致拿不到数据,查询很慢。
第二、查询的时间点,恰好数据库在刷新脏页,我们知道数据库进行了更新操作之后,不会立刻将这些数据进行落盘,而是刷新到redo log中去,等到空闲的时候,通过redo log里面的日志将数据同步到磁盘中去。
第三、当前时间点正在进行一个大的备份任务,导致磁盘的IO突然增高,内存和磁盘的交互速度变慢,自然而然,查询的速度也就降下来了。
第四、其他未知的神秘力量,例如大表操作、大事务、网络带宽被占用等等
如果是一直这么慢,一般情况下,SQL层面的问题可能比较大,而SQL层面,首先要考虑这几个要素:
1、表的数据量有多大?上亿条还是只有几万条?如果是上亿条,那还说得过去,如果是几万条但是速度很慢,大概率是SQL质量太差。
2、是否向数据库请求了不必要的数据,例如只需要100条数据,但是却扫描了全表。也就是扫描行数和返回的行数相差太多,这个时候需要重新写SQL,避免不必要的数据访问。
3、表中的字段是否拥有索引?SQL是否使用到了索引?这种情况下,我们可以使用explain的方法查看该SQL的执行计划,然后查看执行计划中的rows列和type列,其中rows列反映的是扫描的行数,而type列反映的是扫描该表的方法。如果表没有建立索引,应该根据SQL的具体内容,为表创建相关的索引。
4、SQL中是否使用了很多的关联查询和子查询,例如SQL中包含很多个left join或者对于一个大的结果集进行group by操作。
5、如果表使用到了索引,就要看看索引创建的合理性了,是否在一些基数比较小的字段上创建了索引,导致索引的作用没有发挥出来,还导致"回表"操作,引发性能损耗,此时,修改表的索引就变得很必要了。
6、比较隐蔽的一个问题,就是字段上如果进行了数据的类型转换或者函数操作,那么这个SQL是用不到该字段的索引的,这个问题很早之前遇到过几次,当时也是迟迟查不出来原因,例如:
select xxx from tbl where a*2 < 4 和 select xxx from tbl where a<2 是不一样的,如果在a列上指定了索引,那么前者不会用到索引,而后者才能用到索引。
分析完这些,如果性能还是比较差,那就应该针对SQL进行优化了,通过explain查看当前的查询类型,针对SQL进行重写。如果重新之后还是不行,那可能就得从业务层面进行调整了,不过一般不用走到这一步,SQL的问题都能够解决。
很多时候,SQL的慢是有多种原因结合起来造成的,例如,当你的表中数据量很大的时候,你的SQL需要从中过滤很大一部分数据,但是buffer pool的值又设置的很小,因此内存和磁盘之间需要频繁的交换数据,这当然会影响你的SQL性能。
说一个真实的例子,这两天线上有个环境的查询性能一直很不好,我分析了那个SQL,发现SQL也走了索引,而且过滤的行数也不是特别多,一度百思不得其解,最后查出来的原因让人哭笑不得,交付MySQL实例的时候,脚本的bug,导致my.cnf中buffer pool的值被设置为0M,然后MySQL就给了个默认值5M的buffer pool,才导致了问题的发生。所以还是具体问题具体分析的好,上面的分析思路,是一种相对来讲比较通用的分析过程,如果有遗漏的地方,还请大家留言补充。