07-索引的应用
一、普通索引的管理实战
1、添加索引
索引本来就是表的一个属性,需要在表的列上建索引
alter table student add index idx_cid(classid);
在classid这个列创建一个名为idx_cid这样一个索引
2、查询索引
show index from student; desc student;
3、删除索引
alter table student drop key idx_cid;
二、主键索引创建
主键:非空且唯一
建表时增加主键索引
两种写法:
写法一:
CREATE TABLE `test` ( `id` int(4) NOT NULL AUTO_INCREMENT, `name` char(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
写法二:
create table t1(id int not null auto_increment primary key);
建表后增加索引
CREATE TABLE `test1` ( `id` int(4) NOT NULL, `name` char(20) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=UTF8; -- 增加自增主键 alter table test1 change id id int(11) primary key not null auto_increment;
三、唯一索引
内容唯一,但不是主键。
alter table student add unique key uni_tel(telnum);
必须保证关于这一列的值是唯一的,怎么判断是不是唯一的
-- 1、总行数查询 select count(*) from world.city; -- 2、基于某个列去重复之后还剩多少行 如果少了就说明有重复的 select count(distinct population) from world.city;
既是一种索引,又是一种约束
四、前缀索引及联合索引
1、前缀索引
比如,我们现在是要在一张表的一个列上创建索引
这个列的长度可能是 varchar(255)
只是举个例子,有可能经常拿这个列进行查询,符合建立索引的前提,经常查询的列创建索引会快一些
叶子节点要排序记录上层的指针信息,排序的过程是要读取所有列值的,如果按照255个字符进行排序需要比1-100更多的时间
有时对比前几个字母就能知道具体排序,如果全量加载列,花费了额外的成本,可以用前缀索引
-- 根据字段的前N个字符建立索引 -- 添加一个列 alter table student add note varchar(200); -- 为这个列创建前缀索引 alter table student add index idx_note(note(10));
2、联合索引
多个字段建立一个索引
比如where后面多条件可以使用联合索引
where a='a' and b='b' and c='c'
联合索引优于单列索引
如果为a b c单独创建索引, 只会走第一个索引
index(a,b,c)
哪个索引用的多一些,谁就放在前面
特点: 前缀生效特性
a, ab, abc, ac 都是可以走索引或部分走索引
a 走a的索引
ab 走ab的索引
abc 走abc的索引
ac 只会走a的索引
ac性能会略差一些,会出现文件排序,(执行计划)
核心条件放在后面了,不走索引,这是优化器决定的,我们干预不了
b bc c ca ba 不走索引
创建联合索引
create table people(id int not null auto_increment primary key , name varchar(20),gender enum('m','f'),shengao int,tizhong int); alter table people add index idx_gst(gender,shengao,tizhong);


创建联合索引只能看见一个最核心的列
五、执行计划explain
explain:获取执行计划,解析器解析出来的执行计划,就是sql语句的执行方法
执行方法大体也就分为两类: 全表扫描, 索引扫描
explain获取的是哪个执行计划??
获取优化器选择后的执行计划
基本语法:
mysql> select * from tb_student; +----+------+-----+-----+------------+-------------+ | id | name | sex | age | class_null | description | +----+------+-----+-----+------------+-------------+ | 1 | cc | 1 | 22 | ww | ww | | 2 | bb | 0 | 44 | tt | tt | | 3 | whc | 1 | 11 | qq | ssdd | | 4 | kkk | 1 | 22 | | | | 5 | kkk | 1 | 22 | | | | 6 | eee | 0 | 19 | | eee | | 7 | rrr | 1 | 22 | | | +----+------+-----+-----+------------+-------------+ 7 rows in set (0.01 sec)
mysql> select * from tb_student; +----+------+-----+-----+------------+-------------+ | id | name | sex | age | class_null | description | +----+------+-----+-----+------------+-------------+ | 1 | cc | 1 | 22 | ww | ww | | 2 | bb | 0 | 44 | tt | tt | | 3 | whc | 1 | 11 | qq | ssdd | | 4 | kkk | 1 | 22 | | | | 5 | kkk | 1 | 22 | | | | 6 | eee | 0 | 19 | | eee | | 7 | rrr | 1 | 22 | | | +----+------+-----+-----+------------+-------------+ 7 rows in set (0.01 sec) mysql> explain select id,name from tb_student where name='cc'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: tb_student partitions: NULL type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 6 filtered: 16.67 Extra: Using where 1 row in set, 1 warning (0.01 sec) mysql>
加\G 就是以列模式 不加就显示在一行
type:
之前说对于查询来讲一种是全表扫描,一种是索引扫描,ALL就是全表扫描
mysql在使用全表扫描时的性能是极其差的,所以MySQL尽量避免出现全表扫描
全表扫描什么时候出现?
- 业务确实要获取所有数据
- 不走索引,导致的全表扫描
- 没索引
- 索引创建有问题(创建到了无用列)
- 语句有问题(查询语句)
索引扫描有很多种级别,也是通过explain type能看到
type : 表示MySQL在表中找到所需行的方式,又称“访问类型”
常见类型如下:
# index, range, ref, eq_ref, const, system,Null
从左到右,性能从最差到最好,我们认为至少要达到range级别
1、index:Full Index Scan(索引全扫描),index与ALL区别为index类型只遍历索引树
2、range:索引范围扫描,对索引的扫描开始于某一点,返回匹配值域的行。
显而易见的索引范围扫描是带有between或者where子句里带有<,>查询。
where条件后 > < >= <= in or between and
我们在使用索引时,最低应当达到range

use world;
alter table city add index idx_popu(population); explain select * from city where population >5000000; explain select * from city where countrycode in ("CHN","JPN");
当mysql使用索引去查找一系列值时,例如IN()和OR列表,也会显示range(范围扫描),当然性能上面是有差异的。
explain select * from test where countrycode in ('chn','jpn');
改写为:
explain select * from city where countrycode='chn'
union
select * from city where countrycode='jpn';
3、ref:使用非唯一索引扫描或者唯一索引的前缀扫描,返回匹配某个单独值的记录行
explain select * from test where countrycode='chn';
4、eq_ref:类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,
就是多表连接中使用primary key或者 unique key作为关联条件
A join B
on A.sid=B.sid
5、const、system:当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。
如将主键置于where列表中,MySQL就能将该查询转换为一个常量
explain select * from city where id=1000;
6、NULL:MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,
例如从一个索引列里选取最小值可以通过单独索引查找完成。没什么用
mysql> explain select name,population from city; +----+-------------+-------+------+---------------+------+---------+------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------+ | 1 | SIMPLE | city | ALL | NULL | NULL | NULL | NULL | 4188 | NULL | +----+-------------+-------+------+---------------+------+---------+------+------+-------+
Extra:
这三个是常见的
Using temporary
Using filesort,
文件排序,即使索引生效了,但是还是会有文件级别的排序,索引在创建的时候会做自动排序
Using join buffer
如果出现以上附加信息:
请检查order by ,group by ,distinct,join 条件列上没有索引
explain select * from city where countrycode='CHN' order by population;
额外信息:
key_len 越小越好
rows 越小越好
六、建立索引的原则
1、数据库索引的设计原则:
为了使索引的使用效率更高,在创建索引时,必须考虑在哪些字段上创建索引和创建什么类型的索引。
那么索引设计原则又是怎样的?
1.1.选择唯一性索引
唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。
例如,学生表中学号是具有唯一性的字段。为该字段建立唯一性索引可以很快的确定某个学生的信息。
如果使用姓名的话,可能存在同名现象,从而降低查询速度。
主键索引和唯一键索引,在查询中使用是效率最高的。
select count(*) from world.city; select count(distinct countrycode) from world.city; select count(distinct countrycode,population ) from world.city;
注意:如果重复值较多,可以考虑采用联合索引
1.2.为经常需要排序、分组和联合操作的字段建立索引
经常需要ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段,排序操作会浪费很多时间。
如果为其建立索引,可以有效地避免排序操作。
1.3.为常作为查询条件的字段建立索引
如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的查询速度。因此,
为这样的字段建立索引,可以提高整个表的查询速度。
- 经常查询
- 列值的重复值少
注:如果经常作为条件的列,重复值特别多,可以建立联合索引。
1.4.尽量使用前缀来索引
如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT和BLOG类型的字段,进行全文检索
会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度。
以上的是重点关注的,以下是能保证则保证的
1.5.限制索引的数目
索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。
修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间。
1.6.删除不再使用或者很少使用的索引
表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理
员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。
2、不走索引的情况(开发规范)
重点关注:
2.1. 没有查询条件,或者查询条件没有建立索引
select * from tab; -- 全表扫描。 select * from tab where 1=1;
在业务数据库中,特别是数据量比较大的表。
是没有全表扫描这种需求。
- 对用户查看是非常痛苦的。
- 对服务器来讲毁灭性的。
(1)select * from tab; -- SQL改写成以下语句: selec * from tab order by price limit 10 需要在price列上建立索引 (2) select * from tab where name='zhangsan' name列没有索引 -- 改: -- 1、换成有索引的列作为查询条件 -- 2、将name列建立索引
2.2 查询结果集是原表中的大部分数据,应该是30%以上。
查询的结果集,超过了总数行数30%,优化器觉得就没有必要走索引了。
假如:tab表 id,name id:1-100w ,id列有索引
select * from tab where id>500000;
如果业务允许,可以使用limit控制。
怎么改写 ?
结合业务判断,有没有更好的方式。如果没有更好的改写方案
尽量不要在mysql存放这个数据了。放到redis里面。
2.3 索引本身失效,统计数据不真实
索引有自我维护的能力。
对于表内容变化比较频繁的情况下,有可能会出现索引失效。
2.4 查询条件使用函数在索引列上,或者对索引列进行运算,运算包括(+,-,*,/,! 等)
例子:
错误的例子:
select * from test where id-1=9;
正确的例子:
select * from test where id=10;
2.5 隐式转换导致索引失效.这一点应当引起重视.也是开发中经常会犯的错误.
这样会导致索引失效. 错误的例子:
mysql> alter table tab add index inx_tel(telnum); Query OK, 0 rows affected (0.03 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> desc tab; +--------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------+-------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | name | varchar(20) | YES | | NULL | | | telnum | varchar(20) | YES | MUL | NULL | | +--------+-------------+------+-----+---------+-------+ 3 rows in set (0.01 sec)
mysql> select * from tab where telnum='1333333'; +------+------+---------+ | id | name | telnum | +------+------+---------+ | 1 | a | 1333333 | +------+------+---------+ 1 row in set (0.00 sec) mysql> select * from tab where telnum=1333333; +------+------+---------+ | id | name | telnum | +------+------+---------+ | 1 | a | 1333333 | +------+------+---------+ 1 row in set (0.00 sec)
字段定义的是varchar类型,where条件用字符串查或用数字查都能查到
mysql> explain select * from tab where telnum='1333333'; +----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+ | 1 | SIMPLE | tab | ref | inx_tel | inx_tel | 63 | const | 1 | Using index condition | +----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+ 1 row in set (0.00 sec) mysql> explain select * from tab where telnum=1333333; +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | tab | ALL | inx_tel | NULL | NULL | NULL | 2 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ 1 row in set (0.00 sec)
前者type为ref走了索引,后者type为ALL全表扫描
mysql> explain select * from tab where telnum=1555555; +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | tab | ALL | inx_tel | NULL | NULL | NULL | 2 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ 1 row in set (0.00 sec) mysql> explain select * from tab where telnum='1555555'; +----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+ | 1 | SIMPLE | tab | ref | inx_tel | inx_tel | 63 | const | 1 | Using index condition | +----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+ 1 row in set (0.00 sec)
2.6 <> ,not in 不走索引
EXPLAIN SELECT * FROM teltab WHERE telnum <> '110'; EXPLAIN SELECT * FROM teltab WHERE telnum NOT IN ('110','119');
mysql> select * from tab where telnum <> '1555555'; +------+------+---------+ | id | name | telnum | +------+------+---------+ | 1 | a | 1333333 | +------+------+---------+ 1 row in set (0.00 sec)
单独的>,<,in 有可能走,也有可能不走,和结果集有关,尽量结合业务添加limit
or或in 尽量改成union
EXPLAIN SELECT * FROM teltab WHERE telnum IN ('110','119');
改写成:
EXPLAIN SELECT * FROM teltab WHERE telnum='110' UNION ALL SELECT * FROM teltab WHERE telnum='119'
2.7 like "%_" 百分号在最前面不走
EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '31%' 走range索引扫描 EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '%110' 不走索引
%linux%类的搜索需求,可以使用elasticsearch
2.8 单独引用复合索引里非第一位置的索引列.
列子:
复合索引:
DROP TABLE t1 CREATE TABLE t1 (id INT,NAME VARCHAR(20),age INT ,sex ENUM('m','f'),money INT); ALTER TABLE t1 ADD INDEX t1_idx(money,age,sex); DESC t1 SHOW INDEX FROM t1
mysql> DESC t1; +-------+---------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+---------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | NAME | varchar(20) | YES | | NULL | | | age | int(11) | YES | | NULL | | | sex | enum('m','f') | YES | | NULL | | | money | int(11) | YES | MUL | NULL | | +-------+---------------+------+-----+---------+-------+ 5 rows in set (0.00 sec) mysql> SHOW INDEX FROM t1; +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | t1 | 1 | t1_idx | 1 | money | A | 0 | NULL | NULL | YES | BTREE | | | | t1 | 1 | t1_idx | 2 | age | A | 0 | NULL | NULL | YES | BTREE | | | | t1 | 1 | t1_idx | 3 | sex | A | 0 | NULL | NULL | YES | BTREE | | | +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 3 rows in set (0.01 sec)
走索引的情况测试:
EXPLAIN SELECT NAME,age,sex,money FROM t1 WHERE money=30 AND age=30 AND sex='m'; EXPLAIN SELECT NAME,age,sex,money FROM t1 WHERE money=30 AND age=30 ; EXPLAIN SELECT NAME,age,sex,money FROM t1 WHERE money=30 AND sex='m'; ----->部分走索引
不走索引的:
EXPLAIN SELECT NAME,age,sex,money FROM t1 WHERE age=20 EXPLAIN SELECT NAME,age,sex,money FROM t1 WHERE age=30 AND sex='m'; EXPLAIN SELECT NAME,age,sex,money FROM t1 WHERE sex='m';

浙公网安备 33010602011771号