优化器的作用有哪些 优化的目的和意义

优化器的作用有哪些 优化的目的和意义

一、前言

最近一直在思考一个问题,mysql经过了10多年的发展了,一直是这么火,可以在互联网界真是王者的存在,即使如火如荼的NOSQL也没办法撼动mysql 王者的地位。

这就萌生了我想从宏观以及微观去认真分析学习下MYSQL,一方面通过宏观的剖析,能够对MYSQL有整体上的把握,另一方面通过微观的分析,对MYSQL有更深刻的认识,提升自己认知水平。

天下武功唯快不破,快体现在MYSQL的索引,高并发,分库分表,读写分离上。但mysql是怎么去去用索引,怎么选择索引呢,为啥mysql的子查询不走索引呢,为啥有些明明有索引却不走索引,MYSQL对索引的使用导致是基于什么准则呢?MYSQL的优化器就为我们来解答这些疑问。

二、mysql 执行流程

相信很多人在学习mysql 的时候,都不是特别关心优化器,咋一听,很懵逼。

下面首先看下MYSQL sql 语句的执行流程

这其中查询优化器部分就是MYSQL的优化器,它的作用是:

优化器的目的是按照一定原则来得到她认为的目标SQL在当前情形下最有效的执行路径,优化器的目的是为了得到目标SQL的执行计划.

三、优化器类型

传统关系型数据库里面的优化器分为CBOM与RBO

3.1

RBO--- Rule_Based Potimizer 基于规则的优化器

RBO所用的判断规则是一组内置的规则,这些规则是硬编码在数据库中的,RBO会根据这些规则从查询SQL诸多的可能的路径中来选择一条作为执行计划(比如在RBO里面,有这么一条规则:有索引使用索引。那么所有带有索引的表在任何情况下都会走索引),RBO有很明显的毛病扩展性与灵活性都不够,并没有考虑到目标SQL所涉及到的实际数量。

3.2、CBO---Cost_Based Potimizer 基于成本的优化器

CBO在会从目标诸多的执行路径中选择一个成本最小的执行路径来作为执行计划。这里的成本他实际代表了MySQL根据相关统计信息计算出来目标SQL对应的步骤的IO,CPU等消耗。也就是意味着数据库里的成本实际上就是对于执行目标SQL所需要IO,CPU等资源的一个估计值。而成本值是根据索引,表,行的统计信息计算出来的。(计算过程比较复杂)

3.3

Cardinality 基数

Cardinality

是CBO特有的概念,

索引基数,代表某个索引字段(包括复合索引)中不同数值(数值组合)的个数。相当于Distinct

show index from baseline G;

*************************** 1. row ***************************

Table: baseline

Non_unique: 0

Key_name: PRIMARY

Seq_in_index: 1

Column_name: id

Collation: A

Cardinality: 223677

Sub_part: NULL

Packed: NULL

Null:

Index_type: BTREE

Comment:

Index_comment:

Id 列的不同数值的个人为223677。若是对性别加索引,这个值就为2。

3.4

Selectivity

可选择率

可选择率也是CBO特有的概念,

selectivity(可选择率)=索引基数和总行数的比值

。公式如下:

Selectivity = cardinality/(numberof rows) * 100%

Selectivity 值越大,使用索引的概率越高。假如是主键id,Selectivity几乎为1,假如是性别,Selectivity 值就会非常小了。

四、explain 信息

mysql> explain select * from visit where project_id=2 and hospital_id=3 G;

*************************** 1. row ***************************

id: 1.

select_type: SIMPLE

table: visit

type: ref

possible_keys: u_pid_hid_patient_visit_monitor,i_project_hospital_monitor,i_project_hospital_date,i_project_hospital_monitor_date

key: i_project_hospital_monitor

key_len: 8

ref: const,const

rows: 644370

Extra:

字段解释:

id 查询执行的顺序,id值相同时表示从上向下执行,如果是子查询,id值会递增,id值越高,优先级越高。

select_type

simple:表示查询中不包含子查询或者 unionprimary:当查询中包含任何复杂的子部分,最外层的查询被标记成 primaryderived:在 from 的列表中包含的子查询被标记成 derivedsubquery:在 select 或 where 列表中包含了子查询,则子查询被标记成 subqueryunion:两个 select 查询时前一个标记为 PRIMARY,后一个标记为 UNION。union 出现在 from 从句子查询中,外层 select 标记为 PIRMARY,union 中第一个查询为 DERIVED,第二个子查询标记为 UNIONunionresult:从 union 表获取结果的 select 被标记成 union result 。

table

显示这一行的数据是关于哪张表的

type

system:表中只有一行数据。属于 const 的特例。如果物理表中就一行数据为 ALLconst :查询结果最多有一个匹配行。因为只有一行,所以可以被视为常量。const 查询速度非常快,因为只读一次。一般情况下把主键或唯一索引作为唯一条件的查询都是 consteq_ref:查询时查询外键表全部数据。且只能查询主键列或关联列。且外键表中外键列中数据不能有重复数据,且这些数据都必须在主键表中有对应数据(主键表中数据可以有没有用到的)ref:相比 eq_ref,不对外键列有强制要求,里面的数据可以重复,只要出现重复的数据取值就是 ref。也可能是索引查询。range:把这个列当作条件只检索其中一个范围。常见 where 从句中出现 between、<、in 等。index:这个连接类型对前面的表中的每一个记录联合进行完全扫描(比 ALL 更好,因为索引一般小于表数据)。ALL:这个连接类型对于前面的每一个记录联合进行完全扫描,这一般比较糟糕,应该尽量避免。

possible_keys.

查询条件字段涉及到的索引,可能没有使用

Key

实际使用的索引。如果为 NULL,则没有使用索引,每次都会只选择优化器任务最有可能的索引。

key_len

表示索引中使用的字节数,查询中使用的索引的长度(最大可能长度),并非实际使用长度,理论上长度越短越好。key_len 是根据表定义计算而得的,不是通过表内检索出的。

ref

显示索引的哪一列被使用了,如果可能的话,是一个常量 const。

rows

根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。

Extra列的值:

using filesort需要使用额外的排序得到结果(进行内存排序或者硬盘排序)using index 优化器只需要使用索引就可以返回结果(覆盖索引)using index condition 优化器使用index condition pushdown优化using join buffer 优化器需要在使用join buffer using mrr 优化器使用mrr优化using temporary 优化器需要使用临时表 using where 优化器使用where过滤五、案例分析MYSQL优化器语句使用索引比全表扫描更慢,则不使用缓存。如果列key均匀分布在1和100之间,下面的查询使用索引就不是很好:select * from table_name where key>1 and key<90;mysql 通过计算发现,全表扫只要计算一部分数据就可以,比走索引更快,则不走索引。用or条件,or 前面的条件有索引,而后面的条件没有索引,那就不会被用到索引。此处遵循代价最大模式。Or 后面没有索引,则还是需要全表扫描才能找到这部分数据,即使前or 前面的由索引,还是直接走全表扫描得到全表数据。如果索引列不是复合索引的第一部分,则不使用索引(即不符合最左前缀),例如,复合索引为(key1,key2),则查询select * from table_name where key2='b';将不会使用索引。索引是有序排列的,当我们用key1作为索引查询的时候,对一颗索引树,可以通过二分查找到对应的索引。但我们同key2 查询的时候,我们可能需要遍历所有索引,然后根据索引回表查找对应数据,优化器发现代价有点大,还是直接走全表扫描吧。如果like是以‘%’开始的,则该列上的索引不会被使用。例如select * from table_name where key1 like '%a';该查询即使key1上存在索引,也不会被使用。可以参考最左前缀思考。如果列为字符串,则where条件中必须将字符常量值加引号,否则即使该列上存在索引,也不会被使用。例如,select * from table_name where key1=1;如果key1列保存的是字符串,即使key1上有索引,也不会被使用。WHERE字句的查询条件里有不等于号(WHERE column!=...),或<>操作符,否则将引擎放弃使用索引而进行全表扫描。where 子句中对字段进行 null 值判断 where mobile = null 此查询 不会走索引mysql 对索引为null 的列不包含in 和 not in 也要慎用,否则会导致全表扫描不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引左边有函数,相当于对所有的数据进行运算,这样子索引实际已经不是有序了,查找起来则需要遍历整棵索引树。效率太低了。

六、优化建议

对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t where num is null 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询: select id from t where num=0应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t where num=10 or num=20 可以这样查询: select id from t where num=10 union all select id from t where num=20下面的查询也将导致全表扫描: select id from t where name like '%abc%' 若要提高效率,可以考虑全文检索。In 和 not in 也要慎用,否则会导致全表扫描,如: select id from t where num in(1,2,3) 对于连续的数值,能用 between 就不要用 in 了: select id from t where num between 1 and 3如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描: select id from t where num=@num 可以改为强制查询使用索引: select id from t with(index(索引名)) where num=@num应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如: select id from t where num/2=100 应改为: select id from t where num=100*2应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如: select id from t where substring(name,1,3)='abc'--name以abc开头的idselect id from t where datediff(day,createdate,'2005-11-30')=0--'2005-11-30'生成的id 应改为: select id from t where name like 'abc%' select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。不要写一些没有意义的查询,如需要生成一个空表结构: select col1,col2 into #t from t where 1=0 这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样: create table #t(...)很多时候用 exists 代替 in 是一个好的选择: select num from a where num in(select num from b) 用下面的语句替换: select num from a where exists(select 1 from b where num=a.num)并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。避免频繁创建和删除临时表,以减少系统表资源的消耗。临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。尽量避免大事务操作,提高系统并发能力。

七、总结

由上面我们可以分析得出,其实mysql优化器在索引选择的时候是基于两种方式来确定的。

首先假如这个字段有索引,但可能带来很多不确定性或者说索引B+查询的效率已经不能在O(logN)时间复杂度范围完成了的情况下,mysql优化器果断的走全文扫描。若能在O(logN) 完成,则基于Selectivity计算出来高的索引,使用它。