转载自: https://blog.csdn.net/weixin_39724194/article/details/107515342
索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。
索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。
更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。索引是一个文件,它是要占据物理空间的。、、
例如,数据库里面有20000条记录,现在要执行这么一个查询:SELECT * FROM table where num = 10000。如果没有索引,必须遍历整个表,直到num等于10000的这一行被找到为止;如果在num列上创建索引,MySQL不需要任何扫描,直接在索引中找10000,就可以得知值这一行的位置。可见,索引的建立可以提高数据库的查询速度。
可分为:
主键索引: 数据列不允许重复,不允许为NULL,一个表只能有一个主键。
唯一索引: 数据列不允许重复,允许为NULL值(允许多个null值),一个表允许多个列创建唯一索引。
ALTER TABLE table_name ADD UNIQUE (column);
创建唯一索引ALTER TABLE table_name ADD UNIQUE (column1,column2);
创建唯一组合索引普通索引: 基本的索引类型,没有唯一性的限制,允许为NULL值。
ALTER TABLE table_name ADD INDEX index_name (column);
创建普通索引ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);
创建组合索引全文索引: 是目前搜索引擎使用的一种关键技术。
全文索引类型为FULLTEXT,在定义索引的列上支持值的全文查找,允许在这些索引列中插入重复值和空值。全文索引可以在CHAR、VARCHAR或者TEXT类型的列上创建。在5.6之前,MySQL中只有MyISAM存储引擎支持全文索引,5.6之后MyISAM和InnoDB支持
ALTER TABLE table_name ADD FULLTEXT (column);
创建全文索引索引设计不合理或者缺少索引都会对数据库和应用程序的性能造成障碍,高效的索引对于获得良好的性能非常重要,设计索引时,应该考虑一下:
// 二级索引:非聚集索引
// 一个表最多可以包含64个二级索引。
//多列索引最多允许有16列。
// 超过限制将返回错误。
//错误1070(42000):指定的关键部件太多;最多允许16个零件
A table can contain a maximum of 64 secondary indexes.
A maximum of 16 columns is permitted for multicolumn indexes.
Exceeding the limit returns an error.
ERROR 1070 (42000): Too many key parts specified; max 16 parts allowed
避免对经常更新的表设计过多的索引,并且索引中的列尽可能要少,而对经常用于查询的字段应该创建索引,但要避免添加不必要的字段
频繁更新的列不要设计索引
数据量小的表最好不要使用索引,由于数据较少,查询花费的时间可能比遍历索引时间还要短,索引可能不会产生优化效果
若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)
当唯一性是某种数据本身的特征时,指定唯一索引。使用唯一索引需能确保定义的列的数据完整性,以提高查询速度
在频繁排序或分组(即group by或order by操作)的列上建立索引,如果待排序的列有多个,可以在这些列上建立组合索引
定义有外键的数据列一定要建立索引
最左前缀匹配原则,组合索引非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
对于定义为text、image和bit的数据类型的列不要建立索引。
查询的内容尽量匹配索引,避免回表问题
当没有给表创建索引的时候
索引的数据结构和具体存储引擎的实现有关,在MySQL中使用较多的索引有Hash索引,B+树索引等,而我们经常使用的InnoDB存储引擎的默认索引实现为:B+树索引。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。
假设有一个表index_demo,表中有2个INT类型的列,1个CHAR(1)类型的列,c1列为主键:
CREATE TABLE index_demo(c1 INT,c2 INT,c3 CHAR(1),PRIMARY KEY(c1)) ;
index_demo表的简化的行格式示意图如下:
我们只在示意图里展示记录的这几个部分:
record_type:
表示记录的类型, 0是普通记录、 2是最小记录、 3 是最大记录、1是B+树非叶子节点记录。next_record:
表示下一条记录的相对位置,我们用箭头来表明下一条记录。各个列的值:
这里只记录在 index_demo 表中的三个列,分别是 c1 、 c2 和 c3 。其他信息:
除了上述3种信息以外的所有信息,包括其他隐藏列的值以及记录的额外信息。把一些记录放到页里的示意图就是(这里一页就是一个磁盘块,代表一次IO):
MySQL InnoDB的默认的页大小是16KB
,因此数据存储在磁盘中,可能会占用多个数据页。如果各个页中的记录没有规律,我们就不得不依次遍历所有的数据页。如果我们想快速的定位到需要查找的记录在哪些数据页中
,我们可以这样做 :
以页28
为例,它对应目录项2
,这个目录项中包含着该页的页号28
以及该页中用户记录的最小主键值 5
。我们只需要把几个目录项在物理存储器上连续存储(比如:数组),就可以实现根据主键值快速查找某条记录的功能了。比如:查找主键值为 20 的记录,具体查找过程分两步:
主键值为20的记录在目录项3中
(因为 12 ≤ 20 < 209 ),对应页9
。至此,针对数据页做的简易目录就搞定了。这个目录有一个别名,称为索引
。
我们新分配一个编号为30的页来专门存储目录项记录
,页10、28、9、20专门存储用户记录
:
目录项记录和普通的用户记录的不同点:
现在查找主键值为 20 的记录,具体查找过程分两步:
更复杂的情况如下:
我们生成了一个存储更高级目录项的 页33 ,这个页中的两条记录分别代表页30和页32,如果用户记录的主键值在 [1, 320)
之间,则到页30中查找更详细的目录项记录,如果主键值 不小于320 的话,就到页32中查找更详细的目录项记录。这个数据结构,它的名称是 B+树 。
关于B树和B+树详细可以看数据结构-B树和B+树
简要说下,类似于数据结构中简单实现的HASH表(散列表)一样,当我们在mysql中用哈希索引时,主要就是通过Hash算法(常见的Hash算法有直接定址法、平方取中法、折叠法、除数取余法、随机数法),将数据库字段数据转换成定长的Hash值,与这条数据的行指针一并存入Hash表的对应位置;如果发生Hash碰撞(两个不同关键字的Hash值相同),则在对应Hash键下以链表形式存储。当然这只是简略模拟图。
hash索引底层就是hash表,进行查找时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据。B+树底层实现是多路平衡查找树。对于每一次的查询都是从根节点出发,查找到叶子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据。
因为在hash索引中经过hash函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询。而B+树的的所有节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也类似),天然支持范围。
B和B+树的区别在于,B+树的非叶子结点只包含导航信息,不包含实际的值,所有的叶子结点和相连的节点使用链表相连,便于区间查找和遍历。
真实环境
中一个页存放的记录数量是非常大的(默认16KB),假设指针与键值忽略不计(或看做10个字节),数据占 1 kb 的空间:1600×16=25600
条记录。1600×1600×16=40960000
条记录。B+树的非叶子节点不存储用户记录,只存储目录记录,相对B树每个节点可以存储更多的记录,树的高度会更矮胖,IO次数也会更少。
页内的记录
是按照主键
的大小顺序排成一个单向链表
。页和页之间
也是根据页中记录的主键
的大小顺序排成一个双向链表
。主键+页号
。完整的用户记录
。索引和数据保存在同一个B+树中
,因此从聚簇索引中获取数据比非聚簇索引更快。排序查找
和范围查找
速度非常快。数据都是紧密相连
,数据库可以从更少的数据块中提取数据,节省了大量的IO操作
。自增的ID列为主键
。主键为不可更新
。MyISAM不支持聚簇索引
。每个MySQL的表只能有一个聚簇索引
。非空的唯一索引列代替
。如果没有这样的列,InnoDB会隐式的定义一个主键
作为聚簇索引。主键应选择有序的id
,不建议使用无序的id,比如UUID、MD5、HASH、字符串作为主键,无法保证数据的顺序增长。聚簇索引
,只能在搜索条件是主键值
时才发挥作用,因为B+树中的数据都是按照主键进行排序的,如果我们想以别的列作为搜索条件,那么需要创建非聚簇索引
。
例如,以c2列作为搜索条件
,那么需要使用c2列创建一棵B+树
,如下所示:
其中蓝色的是非聚簇索引值即C2列,黄色是聚簇索引值即C1列,粉色是页号。
页内的记录
是按照从c2列
的大小顺序排成一个单向链表
。
页和页之间
也是根据页中记录的c2列
的大小顺序排成一个双向链表
。
非叶子节点存储的是记录的c2列+页号
。
叶子节点存储的并不是完整的用户记录,而只是c2列+主键
这两个列的值。
一张表可以有多个非聚簇索引:
根据c2列的值查找c2=4的记录,查找过程如下:
根页面44
定位到页42
(因为2 ≤ 4 < 9
)c2列没有唯一性约束
,所以c2=4的记录可能分布在多个数据页中,又因为 2 ≤ 4 ≤ 4
,所以确定实际存储用户记录的页在页34和页35
中。定位到具体的记录
。只存储了c2和c1(主键)
两个列,所以我们必须再根据主键值去聚簇索引中再查找
一遍完整的用户记录。非聚集索引叶节点仍然是索引节点(包括普通索引列和主键索引列),只是有一个指针指向对应的数据块,此如果使用非聚集索引查询,而查询列中包含了其他该索引没有覆盖的列,那么他还要进行第二次的查询,查询节点上对应的数据行的数据。
我们可以通过使用覆盖(联合)索引,来避免回表问题。
select user_name from user_info where birthday = '1991-11-1'
create index index_birthday on user_info(birthday);
create index index_birthday_and_user_name on user_info(birthday, user_name);
MyISAM的索引方式都是非聚簇的,InnoDB只包含1个聚簇索引
在InnoDB存储引擎中,我们只需要根据主键值对聚簇索引进行一次查找就能找到对应的记录,而在MyISAM中却需要进行一次回表操作,意味着MyISAM中建立的索引相当于全部都是二级索引 。
InnoDB的索引文件本身就是数据文件,而MyISAM索引文件和数据文件是分离的 ,索引文件仅保存数据记录的地址。
*.sdi(描述表结构)
、*.MYD(数据)
,*.MYI(索引)
.ibd(表结构、索引和数据都存在一起)
InnoDB的非聚簇索引data域存储相应记录主键的值 ,而MyISAM索引记录的是地址 。换句话说,InnoDB的所有非聚簇索引都引用主键作为data域。
MyISAM的回表操作是十分快速的,因为是拿着地址偏移量直接到文件中取数据的,反观InnoDB是通过获取主键之后再去聚簇索引里找记录,虽然说也不慢,但还是比不上直接用地址去访问。
InnoDB要求表必须有主键 ( MyISAM可以没有 )。如果没有显式指定,则MySQL系统会自动选择一个可以非空且唯一标识数据记录的列作为主键。如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整型
MySQL 5.6引入了索引下推优化,默认开启,使用SET optimizer_switch = 'index_condition_pushdown=off'
;可以将其关闭。官方文档中给的例子和解释如下:
people表中(zipcode,lastname,firstname)构成一个索引
SELECT * FROM people WHERE zipcode=‘95054’ AND lastname LIKE ‘%etrunia%’ AND address LIKE ‘%Main Street%’;
如果没有使用索引下推技术,则MySQL会通过zipcode='95054'
从存储引擎中查询对应的数据,返回到MySQL服务端,然后MySQL服务端基于lastname LIKE '%etrunia%'和address LIKE '%Main Street%'
来判断数据是否符合条件。
如果使用了索引下推技术,则MYSQL首先会返回符合zipcode='95054'
的索引,然后根据lastname LIKE '%etrunia%'和address LIKE '%Main Street%'
来判断索引是否符合条件。如果符合条件,则根据该索引来定位对应的数据,如果不符合,则直接reject掉。有了索引下推优化,可以在有like条件查询的情况下,减少回表次数。
总结:
未开启索引下推:
开启索引下推:在条件查询时,当前索引树如果满足全部筛选条件,可以在当前树中完成全部筛选过滤,得到比较小的结果集再进行回表操作
顾名思义,就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。
a = 1 and b = 2 and c > 3 and d = 4
如果建立(a,b,c,d)顺序的索引,只有abc可以用到索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。where name=’xxx’ and age=xx
这时的话,就利用到索引了,再来思考下where age=xx and name=’xxx‘
这个sql会利用索引吗,按照正常的原则来讲是不会利用到的,但是查询优化器会进行优化,把位置交换下。这个sql也能利用到索引了表字段中有exam_id,paper_id,user_id,三个字段;
对他们创建联合索引:
explain select * from e_user_exam where exam_id = 372 and paper_id = 372 and user_id = 184524124736049152;
explain select * from e_user_exam where exam_id = 372 and paper_id = 372 ;
explain select * from e_user_exam where paper_id = 372
explain select * from e_user_exam where paper_id = 372 and exam_id = 372;
explain select * from e_user_exam where paper_id = 372 and exam_id = 372 and user_id = 184524124736049152 ;
explain select * from e_user_exam where exam_id = 372 and user_id = 184524124736049152 ;
explain select * from e_user_exam where user_id = 184524124736049152 and exam_id = 372
explain select * from e_user_exam where paper_id = 372 and user_id = 184524124736049152;
explain select * from e_user_exam where user_id = 184524124736049152 and paper_id = 372 and exam_id = 372 ;
where a = 1 or b = 1
如果a加了索引,b没有加索引,这里a的索引还是不会生效的--id,name,age;对name 创建索引
select * from user where name like '%明'
--type=all
select name,id from user where name like '%明'
--type=index
– 没有高效使用索引是因为字符串索引会逐个转换成accii码,
– 生成b+树时按首个字符串顺序排序,类似复合索引未用左列字段失效一样,
– 跳过开始部分也就无法使用生成的b+树了
EXPLAIN SELECT * FROM emp WHERE name='123';
EXPLAIN SELECT * FROM emp WHERE name= 123; --索引失效
-- 显示查询分析
EXPLAIN SELECT * FROM emp WHERE emp.name LIKE 'abc%';
EXPLAIN SELECT * FROM emp WHERE LEFT(emp.name,3) = 'abc'; --索引失效
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name = 'abc' ;
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name <> 'abc' ; --索引失效
EXPLAIN SELECT * FROM emp WHERE emp.name IS NULL;
EXPLAIN SELECT * FROM emp WHERE emp.name IS NOT NULL; --索引失效
注意:当数据库中的数据的索引列的NULL值达到比较高的比例的时候
,即使在IS NOT NULL 的情况下 MySQL的查询优化器会选择使用索引,此时type的值是range(范围查询)
-- 将 id>20000 的数据的 name 值改为 NULL
UPDATE emp SET `name` = NULL WHERE `id` > 20000;
– 执行查询分析,可以发现 IS NOT NULL 使用了索引
– 具体多少条记录的值为NULL可以使索引在IS NOT NULL的情况下生效,由查询优化器的算法决定
EXPLAIN SELECT * FROM emp WHERE emp.name IS NOT NULL
应该创建索引,使用的时候尽量使用is null判断。
EXPLAIN SELECT * FROM emp WHERE emp.name IS NULL;
EXPLAIN SELECT * FROM emp WHERE emp.name IS NOT NULL; --索引失效
注意:当数据库中的数据的索引列的NULL值达到比较高的比例的时候
,即使在IS NOT NULL 的情况下 MySQL的查询优化器会选择使用索引,此时type的值是range(范围查询)
-- 将 id>20000 的数据的 name 值改为 NULL
UPDATE emp SET `name` = NULL WHERE `id` > 20000;
– 执行查询分析,可以发现 IS NOT NULL 使用了索引
– 具体多少条记录的值为NULL可以使索引在IS NOT NULL的情况下生效,由查询优化器的算法决定
EXPLAIN SELECT * FROM emp WHERE emp.name IS NOT NULL
不一定会失效,每一条sql具体有没有使用索引 可以通过trace追踪一下
最好还是给上默认值
数字类型的给0,字符串给个空串“”,
group by 使用索引的原则几乎跟order by一致 ,唯一区别: