注:本篇是《高性能Mysql》第三版的读书笔记
查询性能优化
查询性能低下的基本原因就是访问的数据量太多,而有些数据根本就用不着,或者mysql在进行分析时存在大量超过结果行的数据
一些经典案例对性能造成损失
1、查询不需要的记录,返回没有limit
2、多表关联时返回全部列。
3、总是取出全部列。这个需要作出取舍。
4、重复查询相同的数据,这种最好将这种数据进行缓存。
对于mysql最简单衡量查询开销的三个指标如下
- 响应时间
- 扫描的行数
- 返回的行数
响应时间是两个的和:服务时间和排队时间。排队时间指的是获取锁或者IO等到的时间etc.
扫描行数和返回行数这两个就像是hash&链表取数据一样 肯定希望像hash这样 o1的复杂度不需要扫描额外的数据行。
explain 中的type反应了访问类型,访问类型有很多,包括从全表扫描all到范围到索引等等。所代表的访问行数就完全不一样了。
rows代表预估扫描的行数
extra 表示mysql将通过哪种方式筛选存储引擎返回的记录。 using where 表示使用where条件。
mysql可以以三种方式应用where条件,下面为分别好到坏
- 在索引中使用where条件来过滤不匹配的记录,这是在存储引擎层完成的。
- 使用索引覆盖扫描,(using index)来返回记录,直接从索引中过滤了不需要的数据并命中返回的结果,这是在mysql服务器层完成,但无需再回表查询记录。
- 从数据表中返回数据,然后过滤不满足条件的记录(using where)这是在mysql服务器层完成的,mysql需要先从数据表读取记录然后过滤。
select a_id,count(*) from tab group by a_id; 这中分组会扫描大量的数据行并返回少量数据,有很大优化空间
- 使用索引覆盖扫描,把所有需要用到的列放到索引中,这样存储引擎就无须回表获取对应行就可以返回结果了。
- 改变表结构,使用汇总表。这个对于不需要实时展示的数据(接受延迟)很好用。响应时间性能会提高很多倍。
- 重写这个查询。
重构查询的方式
- 可以将一个复杂查询拆分成多个小查询(如果合理的话)
- 切分查询,例如删除旧数据,可以写一个定时任务,每次删点,大大减少了一次删除大量数据对服务器造成的影响。
- 分解关联查询
select * from tag
left join tag_post on tag_post.tag_id = tag.id
left join post on tag_post.id = post.id
where tag.tag = 'mysql'
可以拆解成
select * from tag where tag = 'mysql'
select * from tag_post where tag_id = 123
select * from post where post.id in (1,2,3,4,5)
- 分解关联查询会让缓存的效率更高,如果变化较小就可以很好的服用mysql的查询缓存。用到缓存之后一些步骤就秒执行成了
- 将查询分解后,执行单个查询可以减少锁的竞争。
- 在应用层做关联,可以更容易对数据库进行拆分,提高性能和扩展性。
- 查询本身的效率可能也会提升,in 会按照顺序进行查询,比随机关联更高效。
- 可以减少冗余的记录。
- 相当于在应用层做的哈希关联,效率要比在mysql的嵌套循环高。
一个查询的过程如下
mysql客户端和服务端之间的通讯协议是半双工的,任意时刻,客户端与服务端只能单向发送数据。
一端开始发送消息,另一端接收完整消息才可以响应。
当使用mysql的库函数取数据时,看起来像是从mysql服务器取数据,但是实际上是从库函数缓存取数据。将数据缓存到内存来提高性能。这个查询缓存在不同的编程语言调用mysql的时候都可以设置和指定要不要使用缓存。
对于取大量结果集的操作不适合用查询缓存,库函数会花很多时间和内存存储所有的结果集。
查询状态
对于一个mysql连接或者说一个线程,任何时刻都有一个状态。该状态表示mysql当前正在做什么。command表示状态。
查询缓存
在解析一个查询语句之前,如果查询缓存是打开的,mysql会优先检查这个查询是否命中查询缓存中的数据,这个检查是通过一个对大小写敏感的哈希查找实现的。
查询优化处理
包括:解析sql,预处理,优化sql执行计划。
mysql通过关键字将sql语句进行解析并生成一颗相应的解析树。
下面是一些mysql能够处理的优化类型:
- 重新定义关联表的顺序
- 将外连接转化成内连接
- 使用等价变换规则
- 优化count() min() max()
- 预估并转化为常数表达式
- 覆盖索引扫描
- 子查询优化
- 提前终止查询
- 等值传播
- 列表in()的比较 in会先进行排序。
mysql如何执行关联查询
mysql执行关联查询的策略很简单:mysql对任何关联都执行嵌套循环关联操作,mysql先在一个表中循环取出单条的数据,然后在嵌套循环到下一个表中寻找匹配的行,直到所有表中的行匹配为止。可想而知,被连接表的列有索引是多么关键的!
mysql优化器最重要的一部分就是关联查询优化器,它决定了多个表关联时候的顺序,不同的顺序意味着不同的扫描行数。优化器会将sql的关联表顺序进行优化使其有更少的扫描行数。
排序优化
mysql在排序时是可以使用索引的,不过要遵循索引的使用规则,例如联合索引,要保证排序字段出现顺序与联合索引保持最左匹配法则。
mysql在排序的时候可以使用index或者file sort 尽量保证使用index 性能好, filesort会大量使用内存数据量大则更会使用磁盘。
extra 中包括 using filesort 表示使用了file sort进行排序, using temporary 表示使用了临时表。
5.6版本以后的mysql 在limit和order by 的时候不再对所有结果进行排序,刨除不满足的结果再进行排序,可想而知效率提高了。
索引合并优化
注⚠️:当where条件字句中包含多个复杂条件的时候,mysql能够访问单个表的多个索引以合并和交叉过滤的方式来定位行数据。
优化特定类型的查询
count函数多用来计算列的非null数量或者行数count(*)
对于myisam对count(*)有优化,当然只局限于没有进行where条件查询时,或者对于非null的列。其他列就没有优化了。
优化关联查询
- 确保on或者using子句中的列有索引
- 确保任何group by order by 中的表达式尽量只有一个列,这样更好使用索引进行优化
- 升级mysql时,关联语法、运算符优先级可能会变化
优化group by&&distinct
当无法使用索引时,group by 有两种优化策略,分别为使用临时表或者文件排序来分组。
注:如果没有通过order by 显示指定了排序的列,当查询使用group by 的时候,结果集会按照分组的字段进行排序。这种排序又导致了需要文件排序。也可以group by 时指定 asc desc
优化limit
可以使用上次查询的id 来做where筛选进而达到优化limit的效果。
优化union
union 是去重的,取数据的时候用了临时表,把数据扔到临时表里,再从临时表取数据返回给客户端,
union会对临时表中所有数据进行distinct操作(实际上distinct会对select的所有列去重,因此union是所有列去重)
distinct是遍历比较进行排序去重的,因此数据量大的时候很恐怖,有很大的性能问题。