索引是SQL优化中最重要的手段之一,本文从基础到原理,带你深度掌握索引。html
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构,索引对于良好的性能很是关键,尤为是当表中的数据量愈来愈大时,索引对于性能的影响愈发重要。索引优化应该是对查询性能优化最有效的手段了。索引可以轻易将查询性能提升好几个数量级。java
通俗来说,索引相似文章的目录,用来提升查询的效率。mysql
常见的索引类型有:主键索引、惟一索引、普通索引、全文索引、组合索引算法
当一张表,把某个列设为主键的时候,则该列就是主键索引sql
create table a ( id int primary key auto_increment, name varchar(20) not null default '' );
这里id就是表的主键,若是当建立表时没有指定主键索引,也能够在建立表以后添加:数据库
alter table table_name add primary key (column_name);
用表中的普通列构建的索引,没有任何限制数组
create index 索引名 on table_name(column1); alter table table_name add index 索引名(column1);
全文索引主要针对文本文件,好比文章,标题。在MySQL5.6以前,只有MyISAM存储引擎支持全文索引,MySQL5.6以后InnoDB
存储引擎也支持全文索引。性能优化
create table c( id int primary key auto_increment , title varchar(20), content text, fulltext(title,content) ) engine=myisam charset utf8;
insert into c(title,content) values ('MySQL Tutorial','DBMS stands for DataBase ...'), ('How To Use MySQL Well','After you went through a ...'), ('Optimizing MySQL','In this tutorial we will show ...'), ('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'), ('MySQL vs. YourSQL','In the following database comparison ...'), ('MySQL Security','When configured properly, MySQL ...');
见名知义,索引列中的值必须是惟一的,可是容许为空值。d表中name就是惟一索引,相比主键索引,主键字段不能为null,也不能重复bash
create table d( id int primary key auto_increment , name varchar(32) unique )
用多个列组合构建的索引,这多个列中的值不容许有空值。数据结构
ALTER TABLE 'table_name' ADD INDEX index_name('col1','col2','col3');
组合索引遵循“最左前缀”原则,使用时最好把最经常使用做为检索或排序的列放在最左,依次递减。组合索引至关于创建了col1,col1col2,col1col2col3 三个索引,而col2或者col3是不能使用索引的。在使用组合索引的时候可能由于列名长度过长而致使索引的key太大,致使效率下降,在容许的状况下,能够只取col1和col2的前几个字符做为索引。
ALTER TABLE 'table_name' ADD INDEX index_name(col1(4),col2(3));
表示使用col1的前4个字符和col2的前3个字符做为索引
咱们这里先简单剖析一下索引的机制,为接下来的深刻作一些铺垫。
传统的查询方法,是按照表的顺序遍历的,不论查询几条数据,MySQL须要将表的数据从头至尾遍历一遍。
在咱们添加完索引以后,MySQL通常经过BTREE算法生成一个索引文件,在查询数据库时,找到索引文件进行遍历,使用可以大幅地查询的效率的折半查找的方式,找到相应的键从而获取数据。
建立索引是为产生索引文件的,占用磁盘空间。索引文件是一个二叉树类型的文件,可想而知咱们的DML操做((数据操做语言,对表记录的(增、删、改)操做)一样也会对索引文件进行修改,因此性能会相应的有所降低。
上面已经说到,索引其实是数据库中知足特定查找算法的数据结构
,这些数据结构以某种方式引用(指向)数据,这样就能够在这些数据结构上实现高级查找算法
。
可能咱们都知道,MySQL索引是B+树
数据结构,固然,实际上索引还有哈希表
、有序数组
等常见的数据结构。
哈希表是一种以键-值(key-value)存储数据的结构,咱们只要输入待查找的值即key,就能够找到其对应的值即Value。哈希的思路很简单,把值放在数组里,用一个哈希函数把key换算成一个肯定的位置,而后把value放在数组的这个位置。
不可避免地,多个key值通过哈希函数的换算,会出现同一个值的状况。处理这种状况的一种方法是,拉出一个链表。
因此,须要注意,哈希表后的链表并非有序的,区间查询的话须要扫描链表,因此哈希表这种结构适用于只有等值查询的场景,好比Memcached及其余一些NoSQL引擎。
另一个你们比较熟悉的数组结构,有序数组在等值查询和范围查询场景中的性能都很是优秀。
若是仅仅看查询效率,有序数组是很是棒的数据结构。可是,在须要更新数据的时候就麻烦了,你往中间插入一个记录就必须得挪动后面全部的记录,成本过高。
因此,有序数组索引只适用于静态存储引擎,好比你要保存的是2017年某个城市的全部人口信息,这类不会再修改的数据。
这两种都不是最主要的索引,常见的索引使用的数据结构是树结构,树是数据结构里相对复杂一些的数据结构,咱们来一步步认识索引的树结构。
二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。可是,折半查找要求线性表必须采用顺序存储结构,并且表中元素按关键字有序排列。
查找方法:首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,若是二者相等,则查找成功;不然利用中间位置记录将表分红前、后两个子表,若是中间位置记录的关键字大于查找关键字,则进一步查找前一子表,不然进一步查找后一子表。重复以上过程,直到找到知足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
上面提到的有序数组的等值查询和比较查询效率很是高,可是更新数据存在问题。
为了支持频繁的修改,好比插入数据,咱们须要采用链表。链表的话,若是是单链表,它的查找效率仍是不够高。
因此,有没有可使用二分查找的链表呢?
为了解决这个问题,BST(Binary Search Tree)也就是咱们所说的二叉查找树诞生了。
二叉树具备如下性质:左子树的键值小于根的键值,右子树的键值大于根的键值。
以下图所示就是一棵二叉查找树,
在这种比较平衡的状态下查找时间复杂度是O(log(n))。
可是二叉查找树存在一个问题:在某些极端状况下会退化成链表。
一样是2,3,4,6,7,8这六个数字,若是咱们插入的数据恰好是有序的,那它就变成这样👇
这个时候,二叉查找树查找的时间复杂度就和链表同样,是O(n)。
形成它“叉劈”的缘由是什么呢? 由于左右子树深度差太大,这棵树的左子树根本没有节点——也就是它不够平衡。
因此,咱们有没有左右子树深度相差不是那么大,更加平衡的树呢? ——那就就是平衡二叉树,叫作 Balanced binary search trees,或者 AVL 树。
AVL Trees (Balanced binary search trees) 平衡二叉树的定义:左右子树深度差绝对值不能超过 1。
例如左子树的深度是 2,右子树的深度只能是 1 或者 3。 这个时候咱们再按顺序插入 2,3,4,6,7,8,就不会“叉劈”👇
AVL树的平衡是怎么作到的呢?主要用到了两个操做左旋
、右旋
。
插入 一、二、3。
当咱们插入了 一、2 以后,若是按照二叉查找树的定义,3 确定是要在 2 的右边的,这个时候根节点 1 的右节点深度会变成 2,可是左节点的深度是 0,由于它没有子节点,因此就会违反平衡二叉树的定义。
那应该怎么办呢?由于它是右节点下面接一个右节点,右–右型,因此这个时候咱们要把 2 提上去,这个操做叫作左旋
。
右旋
操做,把 2提上去。既然平衡二叉树能保持平衡,不会退化,那么咱们用平衡二叉树存储索引能够吗?——能够的。
当咱们用树的结构来存储索引的时候,访问一个节点就要跟磁盘之间发生一次 IO。 InnoDB 操做磁盘的最小的单位是一页(或者叫一个磁盘块)。与主存不一样,磁盘I/O存在机械运动耗费,所以磁盘I/O的时间消耗是巨大的。
因此若是每一个节点存储的数据太少,从索引中找到咱们须要的数据,就要访问更多的节点,意味着跟磁盘交互次数就会过多。
那么解决方案是什么?
让每一个节点存储更多的数据。
让节点上有更多的关键字。
节点上的关键字的数量越多,咱们的指针数也越多,也就是意味着能够有更多的分叉(咱们把它叫作“路数”)。
由于分叉数越多,树的深度就会减小(根节点是 0)。 这样,树就从瘦高变成了矮胖。
这个时候,咱们的树就再也不是二叉了,而是多叉,或者叫作多路
。
接下来看一下多路平衡查找树,也就是B树。
B树是一种多叉平衡查找树,以下图主要特色:
以上图为例,咱们来简单看几个查询:
B树看起来很完美,到这就结束了吗?并无。
B树不支持范围查询的快速查找,你想一想这么一个状况若是咱们想要查找10和35之间的数据,查找到15以后,须要回到根节点从新遍历查找,须要从根节点进行屡次遍历,查询效率有待提升。
若是data存储的是行记录,行的大小随着列数的增多,所占空间会变大。这时,一个页中可存储的数据量就会变少,树相应就会变高,磁盘IO次数就会变大
因此接下来就引入咱们的终极数据结构——B+树。
B+树,做为B树的升级版,在B树基础上,MySQL在B树的基础上继续改造,使用B+树构建索引。B+树和B树最主要的区别在于非叶子节点是否存储数据的问题
- B树:非叶子节点和叶子节点都会存储数据。
- B+树:只有叶子节点才会存储数据,非叶子节点至存储键值。叶子节点之间使用双向指针链接,最底层的叶子节点造成了一个双向有序链表。
来看一下InnoDB里的B+树的具体存储结构:
来讲一下这张图的重点:
举个例子:假设一条记录是 1K,一个叶子节点(一页)能够存储 16 条记录。非叶子节点能够存储多少个指针?
假设索引字段是 bigint 类型,长度为 8 字节。指针大小在 InnoDB 源码中设置为 6 字节,这样一共 14 字节。非叶子节点(一页)能够存储 16384/14=1170 个这样的 单元(键值+指针),表明有 1170 个指针。
树深度为 2 的时候,有 1170^2 个叶子节点,能够存储的数据为 1170*1170*16=21902400。
在查找数据时一次页的查找表明一次 IO,也就是说,一张 2000 万左右的表,查询数据最多须要访问 3 次磁盘。
因此在 InnoDB 中 B+ 树深度通常为 1-3 层,它就能知足千万级的数据存储。
咱们来看一下 B+Tree 的数据搜寻过程:
3)添加了指向相邻叶节点的指针,造成了带有顺序访问指针的B+Tree,这样作是为了提升区间查找的效率,只要找到第一个值那么就能够顺序的查找后面的值。
总结一下,InnoDB 中的 B+Tree 的特色:
2)扫库、扫表能力更强(若是咱们要对表进行全表扫描,只须要遍历叶子节点就能够 了,不须要遍历整棵 B+Tree 拿到全部的数据)
MySQL中最多见的两种存储引擎分别是MyISAM和InnoDB,分别实现了非聚簇索引
和聚簇索引
。
首先要介绍几个概念,在索引的分类中,咱们能够按照索引的键是否为主键来分为“主键索引
”和“辅助索引
”,使用主键键值创建的索引称为“主键索引
”,其它的称为“辅助索引
”。所以主键索引
只能有一个,辅助索引能够有不少个。
MyISAM存储引擎采用的是非聚簇索引,非聚簇索引的主键索引和辅助索引基本上是相同的
,只是主键索引不容许重复,不容许空值,他们的叶子结点的key都存储指向键值对应的数据的物理地址。
非聚簇索引的数据表和索引表是分开存储的。
非聚簇索引中的数据是根据数据的插入顺序保存。所以非聚簇索引更适合单个数据的查询。插入顺序不受键值影响。
思考:既然非聚簇索引的主键索引索引和辅助索引指向相同的内容,为何还要辅助索引呢?索引不就是用来查询的吗,用在哪些地方呢?不就是WHERE和ORDER BY 语句后面吗,那么若是查询的条件不是主键怎么办呢,这个时候就须要辅助索引了。
聚簇索引的主键索引的叶子结点存储的是键值对应的数据自己,辅助索引的叶子结点存储的是键值对应的数据的主键键值。所以主键的值长度越小越好,类型越简单越好。
聚簇索引的数据和主键索引存储在一块儿。
从上图中能够看到辅助索引的叶子节点的data存储的是主键的值,主键索引的叶子节点的data存储的是数据自己,也就是说数据和索引存储在一块儿,而且索引查询到的地方就是数据(data)自己,那么索引的顺序和数据自己的顺序就是相同的。
由于聚簇辅助索引存储的是主键的键值,所以能够在数据行移动或者页分裂的时候下降成本,由于这时不用维护辅助索引。可是因为主键索引存储的是数据自己,所以聚簇索引会占用更多的空间。
聚簇索引在插入新数据的时候比非聚簇索引慢不少,由于插入新数据时须要检测主键是否重复,这须要遍历主索引的全部叶节点,而非聚簇索引的叶节点保存的是数据地址,占用空间少,所以分布集中,查询的时候I/O更少,但聚簇索引的主索引中存储的是数据自己,数据占用空间大,分布范围更大,可能占用好多的扇区,所以须要更屡次I/O才能遍历完毕。
第一个叫作列的离散度,咱们先来看一下列的离散度的公式:
count(distinct(column_name)) : count(*)
列的所有不一样值和全部数据行的比例。数据行数相同的状况下,分子越大,列的离散度就越高。
mysql> SELECT * FROM `test`.`user` ORDER BY `id` LIMIT 10 OFFSET 0; +----+-----------+--------+-------------+ | id | name | gender | phone | +----+-----------+--------+-------------+ | 1 | 秦啭 | 0 | 13601722591 | | 2 | 李镒榘 | 0 | 15204160836 | | 3 | 陈艮 | 0 | 13601994087 | | 4 | 沈夷旌 | 0 | 15507785988 | | 5 | 朱桐泰 | 1 | 13201268193 | | 6 | 周韬蕊 | 1 | 15705478612 | | 7 | 冯叻加 | 0 | 13705834063 | | 8 | 王焓 | 1 | 15006956358 | | 9 | 黄芪 | 0 | 15108012536 | | 10 | 吴笄游 | 0 | 15301860708 | +----+-----------+--------+-------------+ 10 rows in set (0.00 sec)
简单来讲,若是列的重复值越多,离散度就越低,重复值越少,离散度就越高。
了解了离散度的概念以后,咱们再来思考一个问题,咱们在 name 上面创建索引和 在 gender 上面创建索引有什么区别。
当咱们用在 gender 上创建的索引去检索数据的时候,因为重复值太多,须要扫描的行数就更多。例如,咱们如今在 gender 列上面建立一个索引,而后看一下执行计划。
ALTER TABLE user ADD INDEX idx_user_gender (gender); -- 耗时比较久 EXPLAIN SELECT * FROM `user` WHERE gender = 0;
+----+-------------+-------------+------------+------+-----------------+-----------------+---------+-------+---------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+-----------------+-----------------+---------+-------+---------+----------+-------+ | 1 | SIMPLE | user | NULL | ref | idx_user_gender | idx_user_gender | 2 | const | 2492574 | 100.00 | NULL | +----+-------------+-------------+------------+------+-----------------+-----------------+---------+-------+---------+----------+-------+ 1 row in set, 1 warning (0.00 sec)
而 name 的离散度更高,好比“陈艮”的这名字,只须要扫描一行。
ALTER TABLE user ADD INDEX idx_user_name (name); EXPLAIN SELECT * FROM `user` WHERE name = '陈艮';
+----+-------------+-------------+------------+------+----------------------------+----------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+----------------------------+----------+---------+-------+------+----------+-------+ | 1 | SIMPLE | user | NULL | ref | idx_name | idx_name | 1023 | const | 1 | 100.00 | NULL | +----+-------------+-------------+------------+------+----------------------------+----------+---------+-------+------+----------+-------+ 1 row in set, 1 warning (0.00 sec)
查看表上的索引,Cardinality [kɑ:dɪ’nælɪtɪ]表明基数,表明预估的不重复的值的数量。索引的基数与表总行数越接近,列的离散度就越高。
mysql> show indexes from user; +-------------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | +-------------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | user | 0 | PRIMARY | 1 | id | A | 4985145 | NULL | NULL | | BTREE | | | | user | 1 | idx_name | 1 | name | A | 2605146 | NULL | NULL | YES | BTREE | | | | user | 1 | idx_user_gender | 1 | gender | A | 1 | NULL | NULL | YES | BTREE | | | | user | 1 | comidx_name_phone | 1 | name | A | 2595718 | NULL | NULL | YES | BTREE | | | | user | 1 | comidx_name_phone | 2 | phone | A | 4972647 | NULL | NULL | YES | BTREE | | | +-------------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 5 rows in set (0.00 sec)
若是在索引 B+Tree 结构里面的重复值太多,MySQL 的优化器发现走索引跟使用全表扫描差不了多少的时候,就算建了索引,也不必定会走索引。
前面咱们说的都是针对单列建立的索引,但有的时候咱们的多条件查询的时候,也会创建组合索引。单列索引能够当作是特殊的组合索引。
好比咱们在 user 表上面,给 name 和 phone 创建了一个组合索引。
ALTER TABLE user add INDEX comidx_name_phone (name,phone);
组合索引在 B+Tree 中是复合的数据结构,它是按照从左到右的顺序来创建搜索树的 (name 在左边,phone 在右边)。
从这张图能够看出来,name 是有序的,phone 是无序的。当 name 相等的时候, phone 才是有序的。
这个时候咱们使用 where name= ‘wangwu‘ and phone = ‘139xx ‘去查询数据的时候, B+Tree 会优先比较 name 来肯定下一步应该搜索的方向,往左仍是往右。若是 name 相同的时候再比较 phone。可是若是查询条件没有 name,就不知道第一步应该查哪一个 节点,由于创建搜索树的时候 name 是第一个比较因子,因此用不到索引。
因此,咱们在创建组合索引的时候,必定要把最经常使用的列放在最左边。 好比下面的三条语句,能用到组合索引吗?
mysql> EXPLAIN SELECT * FROM user WHERE name= '陈艮' AND phone = '13601994087'; +----+-------------+-------------+------------+------+----------------------------+-------------------+---------+-------------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+----------------------------+-------------------+---------+-------------+------+----------+-------+ | 1 | SIMPLE | user_innodb | NULL | ref | comidx_name_phone | comidx_name_phone | 1070 | const,const | 1 | 100.00 | NULL | +----+-------------+-------------+------------+------+----------------------------+-------------------+---------+-------------+------+----------+-------+ 1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM user WHERE name= '陈艮'; +----+-------------+-------------+------------+------+----------------------------+----------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+----------------------------+----------+---------+-------+------+----------+-------+ | 1 | SIMPLE | user_innodb | NULL | ref | comidx_name_phone | idx_name | 1023 | const | 19 | 100.00 | NULL | +----+-------------+-------------+------------+------+----------------------------+----------+---------+-------+------+----------+-------+ 1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM user WHERE phone = '13601994087'; +----+-------------+-------------+------------+------+---------------+------+---------+------+---------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+---------------+------+---------+------+---------+----------+-------------+ | 1 | SIMPLE | user | NULL | ALL | NULL | NULL | NULL | NULL | 4985148 | 10.00 | Using where | +----+-------------+-------------+------------+------+---------------+------+---------+------+---------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
当建立(a,b,c)联合索引时,至关于建立了(a)单列索引,(a,b)组合索引以及(a,b,c)组合索引,想要索引生效的话,只能使用 a和a,b和a,b,c三种组合;固然,b,a也是好使的,由于sql会对它优化。
用 where b=? 和 where b=? and c=? 和 where a=? and c=?是不能使用到索引。不能不用第一个字段,不能中断。
这里就是 MySQL 组合索引的最左匹配原则。
在聚簇索引里,经过辅助索引查找数据,先经过索引找到主键索引的键值,再经过主键值查出索引里面没有的数据,它比基于主键索引的查询多扫描了一棵索引树,这个过程就叫回表。
例如:select * from user where name = ‘lisi’;
在辅助索引里面,无论是单列索引仍是联合索引,若是 select 的数据列只用从索引中就可以取得,没必要从数据区中读取,这时候使用的索引就叫作覆盖索引,这样就避免了回表。
咱们先来建立一个联合索引:
-- 建立联合索引 ALTER TABLE user add INDEX 'comixd_name_phone' ('name','phone');
这三个查询语句都用到了覆盖索引:
EXPLAIN SELECT name,phone FROM user WHERE name= '陈艮' AND phone = '13601994087'; EXPLAIN SELECT name FROM user WHERE name= '陈艮' AND phone = '13601994087'; EXPLAIN SELECT phone FROM user WHERE name= '陈艮' AND phone = '13601994087';
Extra 里面值为“Using index”表明使用了覆盖索引。
mysql> EXPLAIN SELECT name FROM user_innodb WHERE name= '陈艮' AND phone = '13601994087'; +----+-------------+-------------+------------+------+----------------------------+-------------------+---------+-------------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+----------------------------+-------------------+---------+-------------+------+----------+-------------+ | 1 | SIMPLE | user_innodb | NULL | ref | idx_name,comidx_name_phone | comidx_name_phone | 1070 | const,const | 1 | 100.00 | Using index | +----+-------------+-------------+------------+------+----------------------------+-------------------+---------+-------------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
select * ,用不到覆盖索引。
很明显,由于覆盖索引减小了 IO 次数,减小了数据的访问量,能够大大地提高查询效率。
“索引条件下推”,称为 Index Condition Pushdown (ICP),这是MySQL提供的用某一个索引对一个特定的表从表中获取元组”,注意咱们这里特地强调了“一个”,这是由于这样的索引优化不是用于多表链接而是用于单表扫描,确切地说,是单表利用索引进行扫描以获取数据的一种方式。 它的做用以下
一是说明减小完整记录(一条完整元组)读取的个数;
二是说明对于InnoDB汇集索引无效,只能是对SECOND INDEX这样的非聚簇索引有效。
关闭 ICP:
set optimizer_switch='index_condition_pushdown=off';
查看参数:
show variables like 'optimizer_switch';
如今咱们要查询全部名字为陈艮,而且手机号码后四位为4087这我的。查询的 SQL:
SELECT * FROM user WHERE name= '陈艮' and phone LIKE '%4087' ;
这条 SQL 有两种执行方式:
一、根据组合索引查出全部名字是’陈艮’的二级索引数据,而后回表,到主键索引上查询所有符合条件的数据(19 条数据)。而后返回给 Server 层,在 Server 层过滤出手机号码后四位为4087这我的。
二、根据组合索引查出全部名字是’陈艮’的二级索引数据(19 个索引),而后从二级索引 中筛选出手机号码后四位为4087的索引(1 个索引),而后再回表,到主键索引上查询所有符合条件的数据(1 条数据),返回给 Server 层。
很明显,第二种方式到主键索引上查询的数据更少。
注意,索引的比较是在存储引擎进行的,数据记录的比较,是在 Server 层进行的。 而当 phone 的条件不能用于索引过滤时,Server 层不会把 phone 的条件传递 给存储引擎,因此读取了两条没有必要的记录。
这时候,若是知足 name=’陈艮’的记录有 100000 条,就会有 99999 条没有 必要读取的记录。
执行如下 SQL,Using where:
mysql> EXPLAIN SELECT * FROM user WHERE name= '陈艮' AND phone LIKE '%4087'; +----+-------------+-------------+------------+------+-------------------+-------------------+---------+-------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+-------------------+-------------------+---------+-------+------+----------+-------------+ | 1 | SIMPLE | user_innodb | NULL | ref | comidx_name_phone | comidx_name_phone | 1023 | const | 19 | 11.11 | Using where | +----+-------------+-------------+------------+------+-------------------+-------------------+---------+-------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
Using Where 表明从存储引擎取回的数据不所有知足条件,须要在 Server 层过滤。
先用 name条件进行索引范围扫描,读取数据表记录,而后进行比较,检查是否符合 phone LIKE ‘%4087’ 的条件。此时 19 条中只有 1 条符合条件。
由于索引对于改善查询性能的做用是巨大的,因此咱们的目标是尽可能使用索引。
根据上一节的分析,咱们总结出索引建立的一些注意点:
一、在用于 where 判断 order 排序和 join 的(on)字段上建立索引
二、索引的个数不要过多。——浪费空间,更新变慢。
三、区分度低的字段,例如性别,不要建索引。——离散度过低,致使扫描行数过多。
四、频繁更新的值,不要做为主键或者索引。 ——页分裂
五、组合索引把散列性高(区分度高)的值放在前面。——最左前缀匹配原则
六、建立复合索引,而不是修改单列索引。——组合索引代替多个单列索引(因为MySQL中每次只能使用一个索引,因此常用多个条件查询时更适合使用组合索引)
七、过长的字段,怎么创建索引?——使用短索引。
当字段值比较长的时候,创建索引会消耗不少的空间,搜索起来也会很慢。咱们能够经过截取字段的前面一部份内容创建索引,这个就叫前缀索引。
create table shop(address varchar(120) not null); alter table shop add key (address(12));
explain SELECT * FROM 't2' where id+1 = 4;
explain SELECT * FROM 'user' where name = 136; explain SELECT * FROM 'user' where name = '136';
where 条件中 like abc%,like %2673%,like %888 都用不到索引吗?为何?
explain select * from user where name like 'wang%'; explain select * from user where name like '%wang';
过滤的开销太大,因此没法使用索引。这个时候能够用全文索引。
NOT LIKE 不能:
explain select *from employees where last_name not like 'wang'
!= (<>)和 NOT IN 在某些状况下能够:
explain select * from user where id not in (1) explain select * from user where id <> 1
只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。因此咱们在数据库设计时不要让字段的默认值为NULL。
MySQL查询只使用一个索引,所以若是where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。所以数据库默认排序能够符合要求的状况下不要使用排序操做;尽可能不要包含多个列的排序,若是须要最好给这些列建立复合索引。
注意一个 SQL 语句是否使用索引,跟数据库版本、数据量、数据选择度都有关系。
其实,用不用索引,最终都是优化器说了算。
优化器是基于什么的优化器?
基于 cost 开销(Cost Base Optimizer),它不是基于规则(Rule-Based Optimizer),也不是基于语义。怎么样开销小就怎么来。
以上是我对索引相关知识的整理,但愿你能有所收获,参考以下!
参考:
【1】:《高性能MySQL》
【2】:MySQL索引原理及慢查询优化
【3】:极客时间 《MySQL45讲》
【6】:Mysql 四种常见的索引