mysql数据库调优总结(二)

第五、查询优化
1、查询慢的原因:网络因素、CPU、IO、上下文切换、系统调用、生成统计信息、锁等待时间等
2、优化数据库访问:查询性能地下的原因是访问的数据太多,某些查询不可避免的需要筛选大量的数据,可以通过减少访问数据量的方式优化;1、确认 应用程序是否在检索大量超过需要的数据;2、确认mysql服务器层是否在分析大量超过需要的数据行
3、执行过程优化:
1、查询缓存:在解析一个查询语句之前,如果查询缓存是打开的,那么mysql会优先检查这个查询是否命中查询缓存中的数据,如果查 询恰好命中了查询缓存,那么会在返回结果之前会检查用户权限,如果权限没有问题,那么mysql会跳过所有的阶段,就直接从缓存中拿到结果并返回给 客户端;
2、查询优化处理:mysql查询完缓存之后会经过以下几个步骤:解析SQL、预处理、优化SQL执行计划,这几个步骤出现任何的错误,都可能会终止查询;
1、语法解析器和预处理:mysql通过关键字将SQL语句进行解析,并生成一颗解析树,mysql解析器将使用mysql语法规则验证和解析查询,例如验证使用使用了错误的关键字或者顺序是否正确等等,预处理器会进一步检查解析树是否合法,例如表名和列名是否存在,是否有歧义,还会验证权限等等
2、查询优化器:
1、select count() from table;show status like ‘last_query_cost’; 可以看到这条查询语句大概需要做1104个数据页才能找到对应的数据,这是经过一系列的统计信息计算来的(每个表或者索引的页面个数;索引的基数;索引和数据行的长度;索引的分布情况)
2、很多时候mysql会选择错误的执行计划(统计信息不准确(InnoDB因为其mvcc的架构,并不能维护一个数据表的行数的精确统计信息
);执行计划的成本估算不等同于实际执行的成本(有时候某个执行计划虽然需要读取更多的页面,但是他的成本却更小,因为如果这些页面都是顺序读或者这些页面都已经在内存中的话,那么它的访问成本将很小,mysql层面并不知道哪些页面在内存中,哪些在磁盘,所以查询之际执行过程中到底需要多少次IO是无法得知的);mysql的最优可能跟你想的不一样(mysql的优化是基于成本模型的优化,但是有可能不是最快的优化);mysql不考虑其他并发执行的查询;mysql不会考虑不受其控制的操作成本(执行存储过程或者用户自定义函数的成本));
3、优化器的优化策略:1、静态优化,直接对解析树进行分析,并完成优化;2、动态优化,动态优化与查询的上下文有关,也可能跟取值、索引对应的行数有关;3、mysql对查询的静态优化只需要一次,但对动态优化在每次执行时都需要重新评估
4、优化器的优化类型:重新定义关联表的顺序(数据表的关联并不总是按照在查询中指定的顺序进行,决定关联顺序时优化器很重要的功能);将外连接转化为内连接,内连接的效率要高于外连接;使用等价变换规则,mysql可以使用一些等价变化来简化并规划表达式;优化count、min、max索引和列是否可以为空通常可以帮助mysql优化这类表达式:例如,要找到某一列的最小值,只需要查询索引的最左端的记录即可,不需要全文扫描比较;预估并转化为常数表达式,当mysql检测到一个表达式可以转化为常数的时候,就会一直把该表达式作为常数进行处理;索引覆盖扫描,当索引中的列包含所有查询中需要使用的列的时候,可以使用覆盖索引;子查询优化,mysql在某些情况下可以将子查询转换一种效率更高的形式,从而减少多个查询多次对数据进行访问,例如将经常查询的数据放入到缓存中;等值传播:如果两个列的值通过等式关联,那么mysql能够把其中一个列的where条件传递到另一个上:explain select film.film_id from film inner join film_actor using(film_id) where film.film_id > 500;这里使用film_id字段进行等值关联,film_id这个列不仅适用于film表而且适用于film_actor表explain select film.film_id from film inner join film_actor using(film_id) where film.film_id > 500 and film_actor.film_id > 500;
3、关联查询:join的实现原理,Simple Nested-Loop Join,r表示驱动表、s表示驱动表,从图中可以看出从r中拿到数据匹配s开销很大

Index Nested-Loop Join要求非驱动表上有索引,可以通过索引来减少比较,加速查询;在查询时,驱动表(r)会根据关联字段的索引进行查询,当在索引上找到符合的值,在进行回表查询

Block Nested-Loop Join如果有索引会选取第二种方式进行join如果没有索引才会使用Block Nested-Loop Join;
(1)Join Buffer会缓存所有参与查询的列而不是只有Join的列。
(2)可以通过调整join_buffer_size缓存大小
(3)join_buffer_size的默认值是256K,join_buffer_size的最大值在MySQL 5.1.22版本前是4G-1,而之后的版本才能在64位操作系统下申请大于4G的Join Buffer空间。
(4)使用Block Nested-Loop Join算法需要开启优化器管理配置的optimizer_switch的设置block_nested_loop为on,默认为开启。
show variables like ‘%optimizer_switch%’

4、排序优化:无论如何排序都是一个成本很高的操作,所以从性能的角度出发,应该尽可能避免排序或者尽可能避免对大量数据进行排序。
推荐使用利用索引进行排序,但是当不能使用索引的时候,mysql就需要自己进行排序,如果数据量小则再内存中进行,如果数据量大就需要使用磁盘,mysql中称之为filesort。如果需要排序的数据量小于排序缓冲区(show variables like ‘%sort_buffer_size%’😉,mysql使用内存进行快速排序操作,如果内存不够排序,那么mysql就会先将树分块,对每个独立的块使用快速排序进行排序,并将各个块的排序结果存放再磁盘上,然后将各个排好序的块进行合并,最后返回排序结果。
排序算法:两次传值排序,第一次数据读取是将需要排序的字段读取出来,然后进行排序,第二次是将排好序的结果按照需要去读取数据行。
这种方式效率比较低,原因是第二次读取数据的时候因为已经排好序,需要去读取所有记录而此时更多的是随机IO,读取数据成本会比较高
两次传输的优势,在排序的时候存储尽可能少的数据,让排序缓冲区可以尽可能多的容纳行数来进行排序操作。单次传输排序:先读取查询所需要的所有列,然后再根据给定列进行排序,最后直接返回排序结果,此方式只需要一次顺序IO读取所有的数据,而无须任何的随机IO,问题在于查询的列特别多的时候,会占用大量的存储空间,无法存储大量的数据。当需要排序的列的总大小超过max_length_for_sort_data定义的字节,mysql会选择双次排序,反之使用单次排序,当然,用户可以设置此参数的值来选择排序的方式。
4、优化特定类型的优化:
1、优化count查询:没有where条件时,count(
)才是最快的,在myisam的count中;使用近似值,在某些应用场景中,不需要完全精确的值,可以参考使用近似值来代替,比如可以使用explain来获取近似的值;其实在很多OLAP的应用中,需要计算某一个列值的基数,有一个计算近似值的算法叫hyperloglog。更复杂的优化:一般情况下,count()需要扫描大量的行才能获取精确的数据,其实很难优化,在实际操作的时候可以考虑使用索引覆盖扫描,或者增加汇总表,或者增加外部缓存系统。
2、优化关联查询:确保on或者using子句中的列上有索引,在创建索引的时候就要考虑到关联的顺序,当表A和表B使用列C关联的时候,如果优化器的关联顺序是B、A,那么就不需要再B表的对应列上建上索引,没有用到的索引只会带来额外的负担,一般情况下来说,只需要在关联顺序中的第二个表的相应列上创建索引。确保任何的groupby和order by中的表达式只涉及到一个表中的列,这样mysql才有可能使用索引来优化这个过程。
3、优化子查询:子查询的优化最重要的优化建议是尽可能使用关联查询代替
4、优化limit分页:在很多应用场景中我们需要将数据进行分页,一般会使用limit加上偏移量的方法实现,同时加上合适的orderby 的子句,如果这种方式有索引的帮助,效率通常不错,否则的化需要进行大量的文件排序操作,还有一种情况,当偏移量非常大的时候,前面的大部分数据都会被抛弃,这样的代价太高。要优化这种查询的话,要么是在页面中限制分页的数量,要么优化大偏移量的性能。(优化此类查询的最简单的办法就是尽可能地使用覆盖索引,而不是查询所有的列);
5、优化union查询:mysql总是通过创建并填充临时表的方式来执行union查询,因此很多优化策略在union查询中都没法很好的使用。经常需要手工的将where、limit、order by等子句下推到各个子查询中,以便优化器可以充分利用这些条件进行优化;除非确实需要服务器消除重复的行,否则一定要使用union all,因此没有all关键字,mysql会在查询的时候给临时表加上distinct的关键字,这个操作的代价很高。
第六、分区表
1、分区表应用场景:表非常大以至于无法全部都放在内存中,或者只在表的最后部分有热点数据,其他均是历史数据;分区表的数据更容易维护(批量删除大量数据可以使用清除整个分区的方式;对一个独立分区进行优化、检查、修复等操作);分区表的数据可以分布在不同的物理设备上,从而高效地利用多个硬件设备;可以使用分区表来避免某些特殊的瓶颈(innodb的单个索引的互斥访问;ext3文件系统的inode锁竞争);可以备份和恢复独立的分区。
2、分区表的权限:一个表最多只能有1024个分区,在5.7版本的时候可以支持8196个分区;在早期的mysql中,分区表达式必须是整数或者是返回整数的表达式,在mysql5.5中,某些场景可以直接使用列来进行分区;如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来;分区表无法使用外键约束。
3、分区表原理:分区表由多个相关的底层表实现,这个底层表也是由句柄对象标识,我们可以直接访问各个分区。存储引擎管理分区的各个底层表和管理普通表一样(所有的底层表都必须使用相同的存储引擎),分区表的索引知识在各个底层表上各自加上一个完全相同的索引。从存储引擎的角度来看,底层表和普通表没有任何不同,存储引擎也无须知道这是一个普通表还是一个分区表的一部分。
select查询
当查询一个分区表的时候,分区层先打开并锁住所有的底层表,优化器先判断是否可以过滤部分分区,然后再调用对应的存储引擎接口访问各个分区的数据
insert操作
当写入一条记录的时候,分区层先打开并锁住所有的底层表,然后确定哪个分区接受这条记录,再将记录写入对应底层表
delete操作
当删除一条记录时,分区层先打开并锁住所有的底层表,然后确定数据对应的分区,最后对相应底层表进行删除操作

4、分区表类型:范围分区;列表分区;列分区;hash分区;key分区;子分区。 5、如何使用分区表:全量扫描数据,不要任何索引,使用简单的分区方式存放表,不要任何索引,根据分区规则大致定位需要的数据为止,通过使用where条件将需要的数据限制在少数分区中,这种策略适用于以正常的方式访问大量数据;索引数据,并分离热点,如果数据有明显的热点,而且除了这部分数据,其他数据很少被访问到,那么可以将这部分热点数据单独放在一个分区中,让这个分区的数据能够有机会都缓存在内存中,这样查询就可以只访问一个很小的分区表,能够使用索引,也能够有效的使用缓存。