一、sql优化分析依据
1、1 什么是执行计划
简单样例:
按数据的分布情况对用户的sql语句进行解析,优化,对查询相关表的使用顺序,以及对每个表使用哪种方式进行数据操作展示。
1、2 依据什么生成执行计划
1、2、1 innodb数据存储的结构 B+树
1、2、2 innodb的存储引擎和索引
可以说数据库必须有索引,没有索引则检索过程变成了顺序查找,O(n)的时间复杂度几乎是不能忍受的。我们非常容易想象出一个只有单关键字组成的表如何使用B+树进行索引,只要将关键字存储到树的节点即可。当数据库一条记录里包含多个字段时,一棵B+树就只能存储主键,如果检索的是非主键字段,则主键索引失去作用,又变成顺序查找了。这时应该在第二个要检索的列上建立第二套索引。 这个索引由独立的B+树来组织,
有两种常见的方法可以解决多个B+树访问同一套表数据的问题,一:聚簇索引(clustered index ),二:非聚簇索引(secondary index)。这两个名字虽然都叫做索引,但这并不是一种单独的索引类型,而是一种数据存储方式。对于聚簇索引存储来说,行数据和主键B+树存储在一起,辅助键B+树只存储辅助键和主键,主键和非主键B+树几乎是两种类型的树。对于非聚簇索引存储来说,主键B+树在叶子节点存储指向真正数据行的指针,而非主键。
1、2、3 page结构
Page是整个InnoDB存储的最基本构件,也是InnoDB磁盘管理的最小单位,与数据库相关的所有内容都存储在这种Page结构里。Page分为几种类型,常见的页类型有数据页(B-tree Node)Undo页(Undo Log Page)系统页(System Page) 事务数据页(Transaction System Page)等。单个Page的大小是16K(编译宏UNIV_PAGE_SIZE控制),每个Page使用一个32位的int值来唯一标识,这也正好对应InnoDB最大64TB的存储容量(16Kib * 2^32 = 64Tib)。
每个Page都有通用的头和尾,但是中部的内容根据Page的类型不同而发生变化。Page的头部里有我们关心的一些数据,
Page的主体内容,我们主要关注行数据和索引的存储,他们都位于Page的User Records部分,User Records占据Page的大部分空间,User Records由一条一条的Record组成,每条记录代表索引树上的一个节点(非叶子节点和叶子节点)。在一个Page内部,单链表的头尾由固定内容的两条记录来表示,字符串形式的"Infimum"代表开头,"Supremum"代表结尾。这两个用来代表开头结尾的Record存储在System Records的段里,这个System Records和User Records是两个平行的段。InnoDB存在4种不同的Record,它们分别是:主键索引树非叶节点 、主键索引树叶子节点 、辅助键索引树非叶节点 和辅助键索引树叶子节点。这4种节点的Record格式有一些差异,但是它们都存储着Next指针指向下一个Record
详细看下不同类型的Record里到底存储了什么数据,根据B+树节点的不同,User Record可以被分成四种格式,下图种按照颜色予以区分
1、3 执行计划解读:
1、id列数字越大越先执行,如果说数字一样大,那么就从上往下依次执行,id列为null的就表是这是一个结果集,不需要使用它来进行查询。
2、select_type列常见的有:
3、table 显示的查询表名,如果查询使用了别名,那么这里显示的是别名,如果不涉及对数据表的操作,那么这显示为null,如果显示为尖括号括起来的<derived N>就表示这个是临时表,后边的N就是执行计划中的id,表示结果来自于这个查询产生。如果是尖括号括起来的<union M,N>,与<derived N>类似,也是一个临时表,表示这个结果来自于union查询的id为M,N的结果集。
4、type
5、possible_keys 查询可能使用到的索引都会在这里列出来
6、key 查询真正使用到的索引,select_type为index_merge时,这里可能出现两个以上的索引,其他的select_type这里只会出现一个。
7、key_len 用于处理查询的索引长度,如果是单列索引,那就整个索引长度算进去,如果是多列索引,那么查询不一定都能使用到所有的列,具体使用到了多少个列的索引,这里就会计算进去,没有使用到的列,这里不会计算进去。留意下这个列的值,算一下你的多列索引总长度就知道有没有使用到所有的列了。要注意,mysql的ICP特性使用到的索引不会计入其中。另外,key_len只计算where条件用到的索引长度,而排序和分组就算用到了索引,也不会计算到key_len中。
二、常见的优化策略
2、1 sql 层面
*当只要一行数据时使用 LIMIT 1 **MySQL数据库引擎会在找到一条数据后停止搜索,而不是继续往后查少下一条符合记录的数据
*千万不要 ORDER BY RAND()
*避免 SELECT *
*应尽量避免在 where 子句中对字段进行表达式操作
*应尽量避免在where子句中对字段进行函数操作
*很多时候用 exists 代替 in 是一个好的选择
*应尽量避免在 where 子句中使用 != 或 <> 操作符
*对于连续的数值,能用 between 就不要用 in
*应尽量避免在 where 子句中使用 or 来连接条件 **如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描
*一般来说,预判一下过滤条件的范围。由于数据库是从后向前解析 SQL 语句的,通常建议把能过滤最多结果的条件放在后面,(不是一定的,mysql会根据索引做一些优化),尽量使过滤数据量大的条件先被执行
*Join语句的优化 **尽可能减少Join语句中的NestedLoop的循环总次数;“永远用小结果集驱动大的结果集”
*ORDER BY的实现与优化 **优化Query语句中的ORDER BY的时候,尽可能利用已有的索引来避免实际的排序计算(order字段尽量出现在条件中,并且有索引),可以很大幅度的提升ORDER BY操作的性能。
*GROUP BY的实现与优化 **由于GROUP BY实际上也同样需要进行排序操作,而且与ORDER BY相比,GROUP BY主要只是多了排序之后的分组操作。当然,如果在分组的时候还使用了其他的一些聚合函数,那么还需要一些聚合函数的计算。所以,在GROUP BY的实现过程中,与ORDER BY一样也可以利用到索引。
2、2 数据库层面
* 为查询缓存优化你的查询 **SQL函数都不会开启查询缓存
*为搜索字段建索引 **有某个字段你总要会经常用来做搜索,为其建立索引
*在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
*永远为每张表设置一个ID **最好的是一个INT型的并设置上自动增加的 AUTO_INCREMENT标志。
*并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
*索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
*使用 ENUM 而不是 VARCHAR **ENUM 类型是非常快和紧凑的。在实际上,其保存的是 TINYINT,但其外表上显示为字符串。这样一来,用这个字段来做一些选项列表变得相当的完美
*尽可能的使用 NOT NULL **NULL需要额外的空间,并且,在你进行比较的时候,你的程序会更复杂
2、3 select语句的执行顺序
Processing Order of the SELECT statement
The following steps show the processing order for a SELECT statement.
1.FROM 2.ON 3.JOIN 4.WHERE 5.GROUP BY 6.WITH CUBE or WITH ROLLUP 7.HAVING 8.SELECT 9.DISTINCT 10.ORDER BY 11.TOP