6.5查询优化器的局限性
mysql优化器是有局限性的,有时需要我们改写查询以提高效率。
6.5.1关联子查询
子查询是mysql一个很不效率的地方。
这一节首先我们需要了解一下相关子查询:内外部查询间有一个关联条件,类似于join中的等值条件
书中说尽量不要使用in来写相关子查询。推荐使用exists与join的方式来替代。
我感到比较迷惑
书中说mysql优化器会将用in非关联子查询解析为exists关联子查询。那么用exists优化in有什么意义呢?
我做的两个测试中exists与in的explain结果是一致的?
Exists与in的不同通过英文的简单理解就可以明白,exists是存在,返回一个bool值,只要
子查询查到有一个值成立就立马返回,能减少子查询的消耗。
关联子查询的效率是很低的,每一条外部查询的数据行都要让内部查询执行一次。而关联查询join则是一次性将n*m的结果全部罗列,还能通过where条件筛选,更容易优化。(自己的理解)
如何用好关联子查询
子查询并不一定是效率不好的,在合适的时候它也可以使用。具体情况应该explain以后再看结果。
6.5.2union的限制
一些情况union无法将where条件下推到内层,例如分语句的limit20。Union的分语句不可能有超过20条被留下,limit20应该是可以下推的,我们需要手动说明。
这里要注意从临时表去除数据顺序是不固定的。
6.5.3索引合并优化
5.0以后索引已经可以通过索引合并和交叉过滤的方式来定位目标行。
类型列中显示为index_merge
6.5.4等值传递
在关联中对于关联条件的筛选可以下推到各个子查询中。
6.5.5并行执行
Mysql无法利用多核特性并行查询。
6.5.6哈希关联
Mysql不支持哈希关联,不过可以通过自实现的哈希索引来实现哈希关联。
6.5.7松散索引扫描
Mysql不支持松散索引扫描。5.0后在某些条件下可以实现松散索引扫描
Extra列显示 using index for group-by
6.5.8最大值最小值优化
Mysql并不能使用索引的来获取极值,即使索引是有序的,他依然进行全表扫描。有时候我们可以用limit加上指定索引来手动指定返回摸个索引的第一行或者前几行。这个问题myisam解决的不错啊。
6.5.9在同一个表上查询和更新
Mysql不允许在同一张表中同时进行查询与更新。
可以注意奥上文中关联子查询是需要两条查询进行嵌套的,当这两条sql一条为更新,一条为查询是就会报错。这时可以采用关联子查询一节中的处理方式将其转换为join查询的。
6.6查询优化器提示(hint)
这一节给出了dba控制sql的执行计划的方式,即hint。这里我不在罗列hint。在使用hint时要通过详细的测试hint是否能带来效率的提升。(delayed, straight_join, use index ,force index, ignore index)
6.7优化特定类型的查询
6.7.1优化count()
Mysql的count()是不统计空值的。只有才确定一行一定不为空的情况下才能统计行数。
Myisam的count()并不总是非常高效的只有没有where时myisam才能将存储的count(col)
值优化为count(*)才能高效的实现count(*)
简单的count优化
在myisam中我们知晓count(col)的代价很小,但我们需要一个count where 的数据时,可以在合适情况下采用总数减去不需要的行数的办法获取count。
使用近似值
这一类的代表为innodb这样的引擎,即通过explain执行计划来估算查询的行数。
跟复杂的优化
可以使用汇总表来记录时常要用到的统计值来优化。
6.7.2优化关联查询
- 关联字段有索引很重要的,但是只有内部查询需要索引,因为外部查询是用不到该列的索引的。
- 确保任何的group by与order by只涉及一个表中的列,这样mysql才能使用索引来优化这个过程。
- Mysql版本的升级可能会影响关联查询的执行方式
6.7.3优化子查询
尽量使用关联查询(醉了前面说那么多子查询还行后面还是让你用关联查询)
6.7.4优化group by和order by
Group by默认会按照分组字段进行排序,如果不需要排序,可以使用 order by NULL 来禁止排序。需要特定顺序也可以使用desc,asc。
Group by 语句中不于分组字段直接关联的都是无意义的返回值,不同的环境,可能有不同的非关联返回值。
Group by with rollup
超级聚合,可以将分组的结果进行一次超级聚合,即聚合后的总计。在分组项有多项式会有更多的组合。不过Group by with rollup 并不是处理此类需求的一个号的解决方式,最好还是通过程序来解决。
6.7.5优化limit分页
Limit能够减少返回的结果集,所以尽可能早的使用order by与limit能有效减少io次数。
对于offset(取数据的起始值)关键字,我们应该尽量不使用,可以用自定义书签的方式,来访问偏后的数据,但是这样往往需要数据字段中有一个可以预估增长方式的值。
6.7.6优化sql_calc_found_rows
这个hint要求sql扫描所有满足where条件的行,再根据limit抛弃不需要的行。这样的需求往往是我们需要提供一个总的记录数才需要给出该hint,
6.7.7优化union查询
一句话用union all而不用union。
使用union会给要求加入结果集的数据唯一,而对所有数据行进行唯一性检查,这样额外的开销。 当然跟好的做法是不使用union而将各个查询的结果返回给客户端,让客户端来实现union(union的结果集会保存在一个临时表中,返回给客户端有需要将临时表中的数据读取出来)。
6.7.8静态查询分析
通过工具为所有的查询语句做一个体检。
6.7.9使用用户自定义变量
使用用户自定义变量可以保存一些查询结果,用来在sql中实现应用程序中的逻辑而不是通过关联查询等等方式来体现逻辑。是一种解决方式,不过在存储过程中使用的比较多。
- 优化排名语句
自定义变量可以作为左值(可以赋值)。
可以通过自定义变量来自实现类似count的功能,甚至可以对’count’的过程加上条件!来实现排名功能!(具体事例书中查看)
- 避免重复查询刚刚插入的数据
标题很迷惑人,其实就是但你需要将你刚刚插入的数据返回时!Mysql没有相应的语法来一次性实现这样的功能(postgreSQL提供update return语法)。我们必须使用一条insert以及一条select两条语句来实现。
而使用变量保存我们刚刚跟新的值的就可以免于select语句的执行。
- 统计更新与插入数量
在Insert on duplicate key update 可以计算update的记录数,通过mysql返回的总行数可以知道insert的记录数。(Insert on duplicate key updatec1=values(c1)+0*(@x:=@x+1))
- 确认取值的顺序
但语句使用到using temporary与using filesort时可能会导致变量的值。可以通过一个字符处理函数将变量赋值包括进去。例如least()。
- 编写偷懒的union
可以通过一个变量的使用来避免第一个union分支已经找到数据的情况下,后续分支依然要执行的情况。
- 用户自定义变量的其他用处
总结:起来就是查询进行中的自定义单条数据判断条件控制。
6.8案例学习
6.8.1使用mysql构建一个对列表
- 应该尽量避免select for update,而应该使用在sql中connection_id()来判断有没有用户正在访问这条记录。
- 如果逻辑允许一个事务中应该先update再select会更好。
6.8.2计算两点间的距离
这个例子告诉我们。
- 少做事,用尽量少的逻辑来实现功能。(指的是少用sql实现)
- 能否使用索引会严重影响查询的效率,不过也不是无脑索引
- 尽可能的让应用程序来完成计算的部分。
体会:
第六章的内容其实就是介绍了sql的优化,以及我们在配合程序编写sql时应该注意什么?
在几个月的工作中,发现sql部分优化往往都不是前期开发的关键,因为,应用程序的处理会让我们比较轻易的将一整条sql拆分成多条简单的sql,在配合合适的索引,sql往往都不会有太大的问题。。但是也许程序发布以后流量打起来sql的问题就来了