您的位置:首页 > 数据库 > MySQL

mysql sql优化的一些思考和结论

2015-09-03 17:36 645 查看
sql优化是每个程序员必须了解的一些基本功,相信每个程序员在日常的工作中都会涉及到一些sql语句的编写,了解sql语句不同特性可以说是对程序员的应用程序运行性能至关重要.

我所在的公司淘宝网,使用的ibatis orm框架是需要每个程序员都编写各自的sql语句. 我也在日常工作中因为随意的sql语句编写吃过很多亏.

后来觉得有必要把sql语句的执行过程做一个全面的了解,并记录下来,在后面的工作和学习中不断温习和巩固这些知识来使自己的sql语句编写来提升应用程序的性能.

同时,给关注我博客的朋友们一些参考依据.因此有了这篇文章.

一 查询的生命周期

要优化sql语句,就必须要知道查询的生命周期.

查询的生命周期大致可以按照顺序来看:

客户端->服务器->解析->生成执行计划->执行->返回结果

执行是生命周期中最重要的阶段,包括大量的检索数据到存储引擎的调用以及调用后的数据处理,排序,分组等.
查询性能低下的根本原因在于访问的数据太多. 

每当遇到sql语句性能低下时,应该问自己应用程序是否访问了太多的行?是否访问了太多的列?

响应时间一般可以归纳为如下的公式: 

响应时间=服务时间+排队时间, 一般重要的等待是IO等待和锁等待。

每次看到select *的时候需要用怀疑的眼光审视,select * 会带来如下问题: 

1), 无法使用覆盖索引.

2), 还会带来额外的IO,内存和CPU消耗.

二 通信协议

mysql客户端和服务器端的通信协议是半双工. 

半双工最大的缺点是:   任意时刻, 只能单边发消息,没办法进行流量控制,一旦开始发消息,另外一端必须接收完整个消息才能响应。客户端没法让服务器停下来。

mysql客户端获取服务器数据结果有2种方式: 

1),  把mysql服务器可以获取到的结果全部缓存在内存里(默认). 

2),  逐行获取需要的数据. 

看上去逐行获取比全部缓存结果有一些优势,因为可以节省客户端的内存,但mysql为什么默认使用的是全部结果缓存呢?

mysql通常需要等到所有的结果数据都已经发送给客户端才能释放这条查询所占用的资源。获取全部结果并缓存可以减少服务器的压力,让查询早点结束,释放资源.

三 语句优化

3.1 in 

先了解下in的执行过程, 在很多数据库系统中,in()完全等价于多个or条件的字句,因为2者完全等价.

但mysql将in()列表中的数据进行排序,然后通过二分查找的方式来确定列表中的值是否满足条件,这是一个o(lgn)的复杂度操作,等价换成or时是O(n),in里比较多的时候比较有优势.

3.2 order by 

当不能使用索引生成排序结果时,mysql需要自己排序. 数据量小于排序缓冲区,在内存中排序,否则需要在磁盘排序,mysql把他们统称为 文件排序(filesort).

排序原理: 

旧版: 读取行指针和需要排序的字段,对其排序,然后再根据排序结果读取所需要的数据行.

缺点:  2次数据传输,第一次是顺序读取,第二次因为读取的是排序后的记录,会产生大量的随机IO,2次传输成本高. 

优点: 排序的时候进来少存储数据,让"排序缓冲区中"尽量多容纳数据进行排序.

新版:  读取所需的所有列进行排序,最后直接返回结果. 4.1之后引入.

优点:  只需要一次顺序IO读取所有的数据,无需任何的随机IO.

缺点:   如果包含的列非常大,会占用大量的额外的空间,而这些列队排序没有用.

另外一个重要的排序是联表排序, 它分为2种情况来执行: 

1, order by 排序字段都来自第一张表,mysql会在关联处理第一张表时,就进行文件排序. extra 列中会有  using filesort.

2, 其它情况, 先将关联结果放到一个临时表中,所有的关联结束后再排序. "using temporary;using filesort".

3.3 limit 

limit的作用是: 

1, 减少IO操作

2, 排序时有limit字段,mysql 不会对所有的结果进行排序. 

排序查询时有limit, limit会在排序之后应用. 

limit 1000,20 mysql需要查询10020条记录后返回20条记录,

3.4 count

count的功能: 

1, 统计某个列值的数量.

2, 统计行数.

统计列值, 需要非null(不统计null).

统计行数, count(*) 会忽略所有的列而直接统计所有的行.  

myISAM count(*) 没有where 条件时,非常快,因为可以直接读取结果.

3.5 union

关联查询如何执行的?嵌套循环关联!

先在一个表中循环取出单条数据,然后再循环嵌套到下一个表中寻找匹配的行,依次下去,直到找到所有表中匹配的行为止。

看下面的例子:

(select * from a order by id)

union all

(select id from b order by id)

limit 20.

会把a中的300条数据和b中的200条数据放在临时表中,再从临时表中取出前20条.

优化器无法优化成下面这样:

(select * from a order by id limit 20)

union all

(select id from b order by id limit 20)

limit 20.

这样,临时表中只会存放a的20条数据和b的20条数据.

四 总结

1, order by 和limit同时存在时,先执行order by 操作, limit的作用是避免全局排序,部分排序即可返回结果.

                2, order by 应尽量使用索引避免排序,否则需要filesort,在内存或者磁盘排序.

                3, union 会产生临时表,采用循环嵌套的方式关联结果并返回数据.  

4, select *操作时需要用怀疑眼光看待,因为无法使用覆盖索引,还会带来io,cpu,内存消耗等.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息