欢迎来到 Kong Xiangqun 的博客

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. 对用户查看是非常痛苦的。
  2. 对服务器来讲毁灭性的。
1select * from tab;

-- SQL改写成以下语句:
selec * from tab order by price limit 10 需要在price列上建立索引

(2select * 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';

 

posted @ 2020-11-12 23:07  kongxiangqun20220317  阅读(254)  评论(0)    收藏  举报