11月30, 2020

【Mysql知识汇总】之常用索引及sql优化

写在前边:索引在mysql中是极其重要的,它可以大大提高数据查询的速度,而且使用起来灵活方便,本章大概聊一下常用的索引类型以及sql优化相关的知识

你懂得越多,你不懂的越多 alt

索引类型

mysql的索引类型从逻辑角度大概可分为:普通索引(normal)、唯一索引(unique)、主键索引、组合索引。

普通索引(normal)

普通索引可以理解为单个字段的索引,可以使用如下命令创建

CREATE INDEX index_name ON table(column(length))

或者修改通过表结构的方式创建

ALTER TABLE table_name ADD INDEX index_name ON (column(length))

或者在创建表的时候创建

CREATE TABLE `table` (
    `id` int(11) NOT NULL AUTO_INCREMENT ,
    `name` char(255) CHARACTER NOT NULL ,
    `content` text CHARACTER NULL ,
    `time` int(10) NULL DEFAULT NULL ,
    PRIMARY KEY (`id`),
    INDEX index_name (title(length))
)

唯一索引(unique)

与普通索引的区别是,索引列的值必须是唯一的,但是可以为空 创建方式:

直接创建:

CREATE UNIQUE INDEX indexName ON table(column(length))   

修改表结构方式:

ALTER TABLE table_name ADD UNIQUE indexName ON (column(length))

创建表时创建

CREATE TABLE `table` (
    `id` int(11) NOT NULL AUTO_INCREMENT ,
    `title` char(255) CHARACTER NOT NULL ,
    `content` text CHARACTER NULL ,
    `time` int(10) NULL DEFAULT NULL ,
    UNIQUE indexName (title(length))
);

主键索引

是唯一索引的一种,要求索引列不能为空值,一般是建表是创建

CREATE TABLE `table` (
    `id` int(11) NOT NULL AUTO_INCREMENT ,
    `title` char(255) NOT NULL ,
    PRIMARY KEY (`id`)
);

组合索引

是指为多个字段创建的索引,遵循最左匹配原则,比如索引列包括a,b,c,那么相当于创建了3个组合索引:a,b,ca,ba。 创建方式:

ALTER TABLE `table` ADD INDEX a_b_c (a,b,c); 

除了这些索引之外,其实在数据结构角度和物理存储角度还包括以下几种

  • 数据结构角度:hash索引、B+树索引、全文索引、R-树索引
  • 物理存储角度:聚集索引、非聚集索引

sql优化

Explain

explain想必大家都用过吧,用来解释sql语句,是否使用了索引,使用了哪种索引,接下来就看一下explain都有哪些字段。 alt

id: select 识别标识符。这是select 查询序列号,查询序列号即为sql语句执行的顺序,比如下面这个sql:

EXPLAIN SELECT * from (SELECT * from t2 limit 10) as d

alt

select_type: select 类型,他有以下几种值

  • simple:他表示简单的select ,没有union和子查询

  • primary:最外面的select ,在有子查询的语句中,最外面的select 查询就是primary,

  • union:union语句的第二个或者说是后面那一个,现执行一条语句

EXPLAIN select * from t2 union select *from t2 limit 10

结果为:

alt

  • dependent union:union 中的第二个或者后面的select语句,取决于外面的查询

  • union result:union 的结果如上所示

table

输出的行所用的表,这个参数显而易见,容易理解

type

连接类型,有多个参数,先从最佳类型到最差类型介绍

  • system:表示仅有一行,这是const类型的特例,平时不会出现,和这个也可以忽略不计

  • const:表最多有一行匹配,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快,记住一定是用到primary key或者unique,并且只检索出两条数据的情况下才会是const,看下面这条语句

    EXPLAIN select * from t2 limit 1
    

    alt

    虽然只搜索一条数据,但是因为没有用到指定的索引,所以不会使用const,继续看看下面这个

    EXPLAIN select * from t2 where id=827
    

    alt

    id是主键,所以使用了const,所以说可以理解为copnst是最优化的;

  • eq_ref:表间连接使用等值比较的列索引为主键或唯一非空索引时(如下面这条sql,t1.id为主键索引)

    EXPLAIN select * from t1,t2  WHERE t1.id=t2.t1_id
    

    alt

  • ref:对于每个来自于前面的表的行组合,所有匹配索引值的行将从这张表中读取。如果联接只使用键的最左边的前缀,或如果键不是unique或primary key ,则使用ref。如果使用的键仅仅匹配少量行,该联接类型是不错的。

    EXPLAIN select * from t1,t2  where t1.status=t2.status
    

    alt

  • ref_or_null:该联接类型如同ref,但是添加了mysql可以专门搜索包含NULL值得行。在解决子查询中经常使用该联接类型的优化。

  • index_merge:该联接类型表示使用了索引合并优化方法,在这种情况下,key列包含了使用的索引的清单,key_len包含了使用的索引的最长的关键元素。

  • unique_subquery

  • index_subquery

  • range:给定范围内的检索,使用一个索引来检查行。看下面两条语句

    EXPLAIN select * from t1  WHERE user_id in ('1001','1002')
    

    alt

    EXPLAIN select * from t1  WHERE driver_id in ('110','111')
    

    alt

user_id有索引,driver_id没有索引,结果是第一条语句的联接类型是range,第二个是ALL,以为是一定范围,所以像between也可以这样联接,很明显

  • index:该联接类型与ALL相同,除了只有索引树被扫描,这通常比ALL快,因为索引文件通常比数据文件小。(也就是说虽然all和index都是读全表,但是index是从索引中读取的,而all是从硬盘中读取的)

当查询只使用作为单索引部分的列时,Mysql可以使用该联接类型。

  • ALL:对于每个来自于优先前的表的行组合,进程完整的表扫描。如果标识第一个没标记const的表,这通常不好,并且通常在它情况下很差,通常可以增加更多的索引而不要使用ALL,使得行能基于前面的表中的常数值或值被检索出。

possible_keys

提示使用哪个索引会在该列表中找到行,不太重要

key

mysql 使用的索引,简单且重要

key_len

mysql使用的索引长度

ref

ref列显示使用哪列或常数与key一起从表中选择行。

rows

显示mysql 执行查询的行数,简单且重要,数值越大越不好,说明没有用好索引。

extra

该列包含mysql解决查询的详细信息

  • distinct:mysql发现第一个匹配行后,停止为当前的行组合搜索更多的行

  • Not exists

  • range checked each record:没有找到适合的索引。

  • using filesort:mysql手册是这么解释”mysql 需要额外的一次传递,以找出如何安排顺序检索行,通过根绝联接类型浏览所有行并为所有匹配where子句的行保存排序关键字和行的指针来完成排序。然后关键字被排序,并按排序顺序检索行“

  • using index:只使用索引树中的信息而不需要进一步搜索读取实际的行来检索表中的信息。

explain select * from uspace_uchome where uid=1的extra为using index(uid建有索引)

explain select * from uspace_uchome where uid=1的extra为using index(groupid 未建立索引)

  • using temporary :为了解决查询,mysql需要创建一个临时表来容纳结果,典型情况如查询包含客户以按不同情况列出列的group by和order by子句时.出现using temporary就说明语句需要优化。

  • using where:where 子句用于限制哪一个行匹配下一个表或发送到客户。除非你专门从表中索取或检查所有行,如果extra值不为using where 并且表示联接类型为ALL或index,查询可能会有一些错误。

  • using sort_union(),using union(...),using intersect(....):这些函数说明如何为index_merge联接类型合并索引扫描

  • using index fro group-by:类似于访问表的using index方式,using index for group-by 表示mysql发现了一个索引,可以用查询group by或distinct查询的所有列,而不要额外搜索硬盘访问实际的表。并且,按最有效的方式使用索引,以便于对每个组,只读取少量索引条目。

使用索引

提高性能优化的索引:覆盖索引、最左前缀索引、索引下推

覆盖索引

在介绍覆盖索引之前,我们先了解下什么是回表,比如有一个user表,有id和name索引,然后有一条sql:

select * from user where name='aaa'

这个时候执行流程是先找到name索引上的“aaa”,然后找到对应的id,最后根据id索引找到对应user记录,这样先查name索引树,又回到主建索引树进行搜索的过程,就叫做回表覆盖索引也就是解决回表的问题,其实也比较好理解,如果我直接select id就不会去主建索引树查询,因为通过name索引可以直接获取到id,其实通过覆盖索引可以减少树的搜索次数,显著提高查询性能。也是在实际开发过程中经常用来优化的手段。

最左前缀索引

-最左匹配原则是针对联合索引,查询时会从左到右依次查找索引,但是需要注意的是如果sql中有范围查询 (>、<、between、like左匹配)等就不能进一步匹配了,后续退化为线性查找。

比如有联合索引(a,b,c,d),查询条件为a=1 and b=2 and c>3 and d=4,根据最左匹配原则,会依次命中a、b、c,但是无法命中d,因为c是范围查询,d已经无法进行排序了。

索引下推(index condition pushdown )

指的是可以在遍历的过程中就对包含的字段先做判断,直接过滤掉不符合条件的数据,减少回表操作,是在Mysql 5.6之后引入的。

比如下面这条sql:

select * from t where a>10 and a<20 and b='xx'

5.6之前的执行过程是:

  • 1、判断a是否大于10且小于20
  • 2、如果步骤1不满足条件,则进行下一条记录。如果步骤1满足条件,则从a的索引树中取得对应的主键进行回表操作
  • 3、回表操作取得整行数据,取b的值判断是否等于’xx’,如果是,取出数据。重复步骤1和步骤2,直至a大于等于20终止

上面的执行过程中如果满足步骤1的数据有100条,但同时满足t.b='xx’的数据只有10条,数据库却需要回表100次。

引入索引下推后,执行过程如下:

  • 1、判断a是否大于10且小于20
  • 2、如果步骤1不满足条件,则进行下一条记录。如果步骤1满足条件,则继续判断b是否等于’‘xx’。如果不满足,则进行下一条记录,如果满足,则从a的索引树中取得对应的主键进行回表操作,取出数据
  • 3、重复步骤1和步骤2,直至a大于等于20终止

总结:在引入索引下推后,整个过程只需要回表10次,大大减少了回表操作。

需要注意的点

  • 不要在索引字段上进行函数操作或使用表达式,比如:

    select * from table where left(title, 6) = ‘aaa’  //使用函数,不会使用索引
    select * from table where id + 1 = 10000  //使用表达式,不会使用索引
    
  • 隐式类型转换不会走索引

    select * from t where id = 1
    

    如果id字段是字符串类型,那么这个查询不会使用索引,因为Mysql底层会使用CAST函数进行类型转换,那么使用函数将不会走索引。

Tips

下列几种情况下有可能使用到索引:

  • 对于创建的多列索引,只要查询条件使用了最左边的列,索引一般就会被使用。
  • 对于使用like的查询,后面如果是常量并且只有%不在第一个字符,索引才可能被使用。
  • 如果使用column_name is null将使用索引

下列几种情况不适合建立索引

  • 表记录太小
  • 经常增删改的表
  • 数据重复且分布平均的表字段
  • 如果条件中有or,即使其中有条件有索引也不会使用。
  • 对于多列索引,不是使用的第一部分,则不会使用索引。
  • like查询是以%开头
  • 如果列类型是字符串,那一定要在条件中将数据使用引号引用起来。否则不使用索引。
  • 如果mysql估计使用全表扫描要比使用索引快,则不使用索引。

下列几种情况适合添加索引:

  • 主键自动建立唯一索引
  • 频繁作为查询条件的字段应该建立索引
  • 查询中与其他表关联的字段,外键关系建立索引
  • 频繁更新的字段不适合创建索引-更新表的同时,同步更新索引
  • Where条件里用不到的字段不创建索引
  • 单键/组合索引的选择问题,who?(在高并发下倾向创建组合索引)
  • 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
  • 查询中统计或者分组字段

索引失效:

  • 全值匹配
  • 最佳左前缀法则
  • 不在索引列上做任何操作(查询条件中含有函数或表达式)
  • 存储引擎不能使用索引中范围条件右边的列
  • 尽量使用覆盖索引,减少select *
  • mysql在使用不等于的时候无法使用索引会导致全表索引
  • Is null,is not null也无法使用索引
  • Like以通配符开头(‘%abc...’)mysql索引失效会变成全表扫描的操作
  • 字符串不加单引号索引失效少用or,用它来连接时会索引失效

个人公众号,目前处于试运营阶段

跪求各位有志之士关注

alt

本文链接:http://blog.keepting.cn/blog//post/mysql_02.html

-- EOF --

Comments