一 索引简介
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。可以得到索引的本质:索引是数据结构。索引的目的在于提高查询效率。
1 索引存储位置
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以以索引文件的形式存储的磁盘上,索引是数据库中用来提高性能的最常用的工具。
2 索引的优势
- 类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的IO成本
- 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗
3 索引的劣势
- 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息
- 实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的
- 索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句
4 索引的分类
1)单值索引
即一个索引只包含单个列,一个表可以有多个单列索引
-- 随表一起建索引:
CREATE TABLE customer (
id INT(10) UNSIGNED AUTO_INCREMENT ,
customer_no VARCHAR(200),
customer_name VARCHAR(200),
PRIMARY KEY(id),
KEY (customer_name)
)
-- 单独建单值索引:
CREATE INDEX idx_customer_name ON customer(customer_name);
-- 删除索引:
DROP INDEX idx_customer_name on customer;
2)唯一索引
索引列的值必须唯一,但允许有空值
-- 随表一起建索引:
CREATE TABLE customer (id INT(10) UNSIGNED AUTO_INCREMENT ,
customer_no VARCHAR(200),
customer_name VARCHAR(200),
PRIMARY KEY(id),
KEY (customer_name),
UNIQUE (customer_no)
);
-- 单独建唯一索引:
CREATE UNIQUE INDEX idx_customer_no ON customer(customer_no);
-- 删除索引:
DROP INDEX idx_customer_no on customer ;
3)主键索引
设定为主键数据库会自动建立索引,innodb为聚簇索引
-- 随表一起建索引:
CREATE TABLE customer (id INT(10) UNSIGNED AUTO_INCREMENT ,
customer_no VARCHAR(200),customer_name VARCHAR(200),
PRIMARY KEY(id)
);
CREATE TABLE customer2 (id INT(10) UNSIGNED ,
customer_no VARCHAR(200),customer_name VARCHAR(200),
PRIMARY KEY(id)
);
-- 单独建主键索引:
ALTER TABLE customer add PRIMARY KEY customer(customer_no);
-- 删除建主键索引:
ALTER TABLE customer drop PRIMARY KEY ;
4)复合索引
即一个索引包括多个列
-- 随表一起建索引:
CREATE TABLE customer (id INT(10) UNSIGNED AUTO_INCREMENT ,
customer_no VARCHAR(200),customer_name VARCHAR(200),
PRIMARY KEY(id),
KEY (customer_name),
UNIQUE (customer_name),
KEY (customer_no,customer_name)
);
-- 单独建索引:
CREATE INDEX idx_no_name ON customer(customer_no,customer_name);
-- 删除索引:
DROP INDEX idx_no_name on customer ;
5 索引的基本语法
1)CREATE创建索引
-- 创建索引
CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX indexName ON table_name(column))
2)ALTER 添加索引
-- 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list):
-- 这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。
ALTER TABLE tbl_name ADD UNIQUE index_name (column_list):
-- 添加普通索引,索引值可出现多次。
ALTER TABLE tbl_name ADD INDEX index_name (column_list):
-- 该语句指定了索引为 FULLTEXT ,用于全文索引。
ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):
3)删除索引
-- 删除:
DROP INDEX indexName ON mytable;
4)查看索引
SHOW INDEX FROM table_name
6 哪些情况需要创建索引
- 主键自动建立唯一索引
- 频繁作为查询条件的字段应该创建索引
- 查询中与其他表关联的字段,外键关系建立索引
- 单键/组合索引的选择问题,组合索引性价比更高
- 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
- 查询中统计或者分组字段
7 哪些情况不要创建索引
- 表记录太少
- 经常增删改的表或者字段;原因:提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件
- where条件中用不到的字段不创建索引
- 过滤性不好的不适合建索引
二 避免索引失效
数据准备
create table `tb_seller` (
`sellerid` varchar (100),
`name` varchar (100),
`nickname` varchar (50),
`password` varchar (60),
`status` varchar (1),
`address` varchar (100),
`createtime` datetime,
primary key(`sellerid`)
)engine=innodb default charset=utf8mb4;
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,`address`, `createtime`)
values('alibaba','阿里巴巴','阿里小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,`address`, `createtime`)
values('baidu','百度科技有限公司','百度小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,`address`, `createtime`)
values('huawei','华为科技有限公司','华为小店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,`address`, `createtime`)
values('itcast','西天取经有限公司','西天取经','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,`address`, `createtime`)
values('itheima','蜀汉集团有限公司','西蜀','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,`address`, `createtime`)
values('luoji','罗技科技有限公司','罗技小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,`address`, `createtime`)
values('oppo','OPPO科技有限公司','OPPO官方旗舰店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,`address`, `createtime`)
values('ourpalm','掌趣科技股份有限公司','掌趣小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,`address`, `createtime`)
values('qiandu','千度科技','千度小店','e10adc3949ba59abbe56e057f20f883e','2','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,`address`, `createtime`)
values('sina','新浪科技有限公司','新浪官方旗舰店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,`address`, `createtime`)
values('xiaomi','小米科技','小米官方旗舰店','e10adc3949ba59abbe56e057f20f883e','1','西安市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,`address`, `createtime`)
values('yijia','宜家家居','宜家家居旗舰店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
select * from tb_seller
1 全值匹配
全值匹配是指查询语句中使用了索引的所有列,并且所有列的值都与索引中的值完全匹配。这意味着查询的条件必须与索引的所有列的值完全一致,才能利用索引进行快速查找。全值匹配可以提高查询性能,减少数据库的工作负载。如果查询语句中的条件没有与索引的所有列完全匹配,那么就无法利用索引进行快速查找,可能需要进行全表扫描,效率较低。因此,在设计数据库表和索引时,需要根据实际情况选择合适的索引,并且编写查询语句时要保证全值匹配,以提高查询效率。
create index idx_name_sta_addr on tb_seller(name,status,address);
explain select * from tb_seller where name='小米科技' and status='1' and address='北京市';
Extrra字段中属性说明
- using index :使用覆盖索引的时候就会出现
- using where:在查找使用索引的情况下,需要回表去查询所需的数据
- using index condition:查找使用了索引,但是需要回表查询数据
- using index ; using where:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据
从结果可以看出,查询使用了索引,但是需要回表进行查看
explain select * from tb_seller where name='小米科技' and status='1' and address='北京市';
2 最佳左前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
explain select * from tb_seller where name='小米科技'
explain select * from tb_seller where name='小米科技' and status='1'
以上2个示例查询语句都是从复合索引(idx_name_sta_addr )最左前列开始的,而且复合索引 (idx_name_sta_addr )包含查询条件的字段,所以可以使用索引
违法最左前缀法则 , 索引失效:
explain select * from tb_seller where status='1'
如果符合最左法则,但是出现跳跃某一列,只有最左列索引生效:
explain select * from tb_seller where name='小米科技' and address='北京市';
最左前缀法则的原理
mysql中InnoDB引擎中,索引基于B+树索引的结构。在B+树索引中,索引的数据是按照索引列的顺序有序存储的。当查询时,MySQL可以利用B+树的有序性,从根节点开始,按照索引列的顺序进行搜索,直到找到满足查询条件的数据行。违反最左前缀会破坏索引的有序性,所以索引无效。
3. 范围查询右边的列,不能使用索引 。
explain select * from tb_seller where name='小米科技' and status>'1' and address='北京市';
说明:根据key_len为410可以看出前面的两个字段name , status 查询是走索引的, 但是最后一个条件address 没有用到索引。同样是因为范围查询后会破会索引的有序性,导致索引失效。
4 不要在索引列上进行运算操作, 索引将失效。
explain select * from tb_seller where substring(name,3,2)='科技'
因为索引的数据是按照特定的排序方式存储的,而运算会改变数据的排序方式或者比较方式,导致索引无法直接进行匹配和查找。MySQL优化器会尽可能地使用索引提高查询性能,但当索引被运算操作影响时,优化器会选择放弃使用索引,而选择其他的查询方式,如全表扫描。
另外,一些函数操作或表达式计算可能会导致索引列的值发生变化,进而无法准确匹配索引中的值。例如,如果在查询时对索引列进行了函数操作或计算,这可能导致实际匹配的值与索引中的值不一致,从而无法使用索引进行准确的查找。
因此,在使用MySQL索引时,应尽量避免对索引列进行运算操作,尽量保持索引列的原始值,以便优化器能够正确地使用索引提高查询性能。如果需要进行运算操作,可以考虑在查询语句中使用索引列的原始值进行运算,或者考虑创建额外的计算列来存储运算结果,并在需要时使用该计算列进行查询。
5 字符串不加单引号,造成索引失效。
explain select name,status,address from tb_seller where name='小米科技' and status=1
说明:由于在查询时,没有对字符串加单引号,MySQL的查询优化器,会自动的进行类型转换,造成索引失效。
6 尽量使用覆盖索引,避免select *
尽量使用覆盖索引(只访问索引的查询(索引列完全包含查询列)),减少select *
explain select * from tb_seller where name='小米科技' and status='1' and address='北京市';
explain select name,status,address from tb_seller where name='小米科技' and status='1' and address='北京市';
上面第一条查询语句,Extra为Using index condition,表示查找使用了索引,但是需要回表查询数据,而第二条语句Extra为Using where; Using index,表示查找使用了索引,需要的数据都在索引列中能找到,所以不需要回表查询数据,为覆盖索引。回表查询会影响查询效率,所以尽量使用覆盖索引。
7 or使索引失效
用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。示例,name字段是索引列 , 而createtime不是索引列,中间是or进行连接是不走索引的 :
explain select * from tb_seller where name='西天取经有限公司' or createtime = '2088-01-01 12:00:00';
因为"or"条件需要对每个条件进行逻辑判断,而不是简单地进行索引匹配。当使用"OR"条件时,优化器可能无法确定哪个条件会返回满足条件的数据,因此可能会选择放弃使用索引,而进行全表扫描来查找匹配的数据。
8 以%开头的Like模糊查询,索引失效。
如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。
explain select * from tb_seller where name like '西天取经有限公司%'
explain select * from tb_seller where name like '%西天取经有限公司'
9 如果MySQL评估使用索引比全表更慢,则不使用索引。
创建单值索引
create index inx_address on tb_seller(address);
查询语句1
explain select * from tb_seller where address='北京市';
查询语句2
explain select * from tb_seller where address='西安市';
数据中绝大部分address都是北京市,使用查询语句1,在这种情况下,优化器可能认为使用索引+回表进行查找的成本大于进行全表扫描,从而放弃使用索引。
10 is NULL , is NOT NULL 有时索引失效。
查询1
explain select * from tb_seller where name is null;
查询2
explain select * from tb_seller where name is not null;
上面查询一索引有效是因为数据中name为null的不存在,或者在其他数据表中如果查询列,建立了索引,但是数据为空值的情况很少,会使用到索引,反之,优化器可能认为使用索引+回表进行查找的成本大于进行全表扫描,从而放弃使用索引。
11 在使用不等于(!= 或者<>),有时无法使用索引会导致全表扫描
explain select * from tb_seller where address !='北京市';
explain select * from tb_seller where address <>'北京市';
explain select * from tb_seller where address <>'西安市';
12 in 走索引, not in 有时索引失效。
explain select * from tb_seller where sellerid in ('baidu','sina');
explain select * from tb_seller where sellerid not in ('baidu','sina');
explain select * from tb_seller where sellerid not in ('oppo','xiaomi','sina');
情况与9,10,11 道理一样。MySQL 会在选择索引的时候进行优化,如果 MySQL 认为全表扫描比走索引+回表效率高, 那么他会选择全表扫描,如果认为走索引的效率高,那么肯定也是会走索引的。总之,如果过滤性不好,索引会失效。
总结
全值匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
LIKE百分写最右,覆盖索引不写*;
不等空值还有OR,索引影响要注意;
VAR引号不可丢,SQL优化有诀窍。