暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Mysql什么情况下不走索引?

波波的小书房 2021-04-21
2944

本文基于Mysql5.7版本和InnoDB存储引擎。


1、InnoDB索引组织表


在InnoDB引擎中,表都是按照主键顺序组织存放的,这种存放方式的表称为索引组织表。InnoDB存储引擎中的表,都有主键,如果没有显式声明主键,则采取以下措施:
  • 该表如果有非空唯一索引,则该列为主键;如果有多个,则取第一个;

  • 如果没有非空唯一索引,则InnoDB自动创建一个大小为6字节的指针作为主键;


为什么推荐使用自增ID做主键而不是UUID?

  • 这是由于B+树的性质决定的,UUID无序,占用空间大,每(磁盘)页能存放的索引少,这会导致磁盘IO次数增加,效率变慢;


什么是堆表?

  • 与索引组织表相对应,堆表是按照行数据的插入顺序存放,无组织,在某些情况下,堆表比索引组织表更快;


2、索引原理


2.1、什么是索引?

  • 索引是一个单独的、存储在磁盘上的数据库结构。

  • InnoDB支持B+树索引;B+树索引由于一个结点存储的是一个磁盘页的数据,因此只能先找到被查找数据所在的页,然后将页读进内存中,最后再在内存中查找;

  • 关于B+树数据结构的相关知识请看 什么是B-树、B树、B+树、B*树?


2.2、B+树索引分类


聚集(主键)索引

辅助(非聚集、二级)索引

  • 普通索引

  • 唯一索引

  • 联合索引


聚集索引和主键索引的相同点:

  • 叶子结点存放所有数据(这是由B+树的性质所决定的);

不同点:

  • 聚集索引的叶子结点存放一整行数据,而辅助索引的叶子结点只存放该列数据和主键


每张表只能有一个聚集索引,如果是select *操作,则优化器更倾向于使用聚集索引,因为聚集索引能在B+树的叶子结点上找到一整行的数据;
聚集索引对于主键的排序和范围查找非常快;
如果采用辅助索引查找,比如:select * from user where name="张三",假设在辅助索引name上通过3次查找得到(name=张三,id=10),再在聚集索引上通过3次查找得到id为10的整行数据,因此一共进行了6次查找;因此直接查找聚集索引比查找辅助索引效率高:聚集索引的叶子结点包含了全部数据


2.3、联合索引原理


如上图所示,联合索引的结点以(a列的值,b列的值)形式存储。

下面看看如何使用联合索引进行查询:

  • select * from tb1 where a=? and b=?,走联合索引,因为a和b在一起是排序的;

  • select * from tb1 where a=?,走联合索引,因为单独的a是排序的;

  • select * from tb1 where b=?,不走联合索引,因为单独的b不是排序的,如上图,b的顺序为1,2,1,4,1,2;


联合索引的好处:

  • 首先,a列和b列都是排好序的;比如我要查询用户最近三次的购买记录,可以使用user_id作为索引,也可以使用(user_id,buy_date)的联合索引;如果使用user_id作为索引,则需要将查询出来的结果再按buy_date进行一次排序,才能查出最近三次的购买记录;而联合索引已经将buy_date排好序了,只需要取最后三条数据,就是用户最新三次的购买记录;


2.4、什么是覆盖索引?


定义:如果能直接从辅助索引上查找到所有想要的数据,而不需要回表(通过主键在聚集索引上做二次查询),就称该辅助索引覆盖了这条select语句,又称索引覆盖;


优化器一般会倾向于使用覆盖索引而不是聚集索引,因为聚集索引占用空间大,磁盘IO次数多;

联合索引可以当做覆盖索引使用。对于联合索引(a,b)而言,使用b作为查询条件则不走索引,但是如果此时没有针对b的索引,且要查询的列该联合索引都有,则优化器会将该联合索引视作覆盖索引(联合索引上单独的b是无序的,所以是从头到尾顺序查找,尽管如此也比在聚集索引上顺序查找效率高),explain的Extra中有Using index就表明使用了覆盖索引;


2.5、操作索引


查看某个表的索引:show index from 表名

下面解释一下各字段的意思:

  • Table:索引所在表名;

  • Non_unique:值为0表示唯一;

  • Key_name:索引名称;

  • Seq_in_index:索引中该列的位置;

  • Column:索引中列的名称;

  • Collation:列以什么方式存储在索引中,值为A或NULL,B+树索引总是A,即排序的;

  • Cardinality:表示该列中唯一值数目的估计值。如果该值非常小,则表示该列重复数据较多;为什么是估计值呢?因为该值不是实时更新的(代价太大),可以用analyze table命令手动更新一次Cardinality值;

  • Sub_part:是否只对列的一部分索引;比如某个列类型为varchar(1000),可以设置只索引该列的前100个字符,则Sub_part的值为100,如果索引的是列的全部,则Sub_part为NULL;

  • Packed:关键字如何被压缩。如果没有被压缩则为NULL;

  • Null:该列是否允许有NULL值;

  • Index_type:索引类型;


在select语句中使用force index(index_name)强制使用某个索引,而user index(index_name)则提示使用某个索引。



3、性能优化


3.1、执行计划

查看sql的执行计划:explain select ...

下面简要介绍一下各字段的意思,详细解释请查阅Mysql5.7官方文档:https://dev.mysql.com/doc/refman/5.7/en/explain-output.html


id:select的查询序列号

select_type:查询类型

  • simple:简单查询,不包括连接查询和子查询

  • primary:主要的查询,最外层的查询

  • subquery:子查询

table:表示查询的表

partitions:命中的分区,如果是非分区表则为NULL

type:表的连接类型

  • const:只返回一行数据,查询速度很快,该列具有唯一索引或主键索引;如果是普通索引,即时真的只返回一行数据,也不会是const;

  • range:范围查询;

  • index:根据辅助索引全表扫描;

  • ALL:无索引(聚集索引)全表扫描;

possible_keys:通过检测where子句获得可用的索引;

key:实际使用的索引;

rows:显示Mysql在表中进行查询时必须检查的行数;

extra:表示Mysql在处理查询时的详细信息;



3.2、索引设计原则


索引优点:

  • 唯一索引可以保证唯一性;

  • 加快速度:查询速度、表与表连接速度、分组和排序的速度;

缺点:

  • 索引需要占用磁盘空间;

  • 索引的维护(增删改)需要时间,尤其是数据量大的时候;


索引设计原则:

  • 索引并非越多越好,索引太多会影响insert、update、delete的性能;

  • 经常更新的列不使用索引,经常查询(逻辑外键、where子句、group by子句、order by子句)的列使用索引;

  • 数据量小的表不使用索引;

  • 重复值很多的列不使用索引;

  • 删除长期未使用的索引,不用的索引会造成不必要的性能消耗;

  • 避免使用冗余索引:如(name,city)和(name)就是冗余索引,因此一般应该扩展已有的索引,而不是创建新的索引;


3.3、索引失效的情况


避免where子句中对字段施加函数,会造成无法命中索引。
  • 这是因为b+树只能对原值进行索引。


索引列尽量不要存在null值,将该列设置not null约束,可以用0代替null。

假设订单表有1000万数据,订单号为null有100条,分析如下三条sql语句:

explain select order_num from goods_order where order_num ='f035a84a-4672-4517-bd8a-2c9f2d92423e'
explain select order_num from goods_order where order_num is null
explain select order_num from goods_order where order_num is not null

复制


通过执行结果发现,只有is not null不走索引。


联合索引遵循最左前缀原则,否则不走索引。

使用in不走索引,用exists代替,如果是连续的数值则可以用between代替

应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,则不走索引。
like子句不能前置百分比,否则不走索引,如果非要前置百分比不可,则考虑使用全文索引。
where 子句中不要使用 != 或 <> ,否则不走索引
limit offset查询缓慢时,可以借助主键索引来提高性能
  • select a.* from bigtable a inner join (select id from bigtable LIMIT 6520000,10) b on a.id=b.id



最后看一下下面这种情况:

比如select * from order where order_id>10000 and order_id<20000,注意order_id不是主键,此时有一个辅助索引order_id,由于该索引上不包含所有的字段(注意是select *),因此还要回表。通过order_id找到的对应主键是无序的,所以回表的过程是磁盘离散读,而磁盘顺序读的速度是远大于离散读的(固态硬盘的随机读速度非常快),特别是数据量大的情况下差异更明显,这时会优化器会放弃辅助索引而走全表扫描。


总体来说,优化器选择走不走索引,也不是千篇一律的,有时候也要看实际情况。对于我们而言,要熟练使用explain查看sql执行计划,在实际项目中分析sql性能的瓶颈,进行优化。

文章转载自波波的小书房,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论