1、索引介绍



1.1、什么是索引


1)索引就好比一本书的目录,它能让你更快的找到自己想要的内容。

2)让获取的数据更有目的性,从而提高数据库检索数据的性能。


1.2、索引类型介绍


  • BTREE:B+树索引

    • B+tree索引

      第六章• MySQL索引管理及执行计划_索引

    • B*tree索引

      第六章• MySQL索引管理及执行计划_MySQL_02

       

  • HASH:HASH索引

  • FULLTEXT:全文索引

  • RTREE:R树索引


1.3、索引管理


索引建立在表的列上(字段)的。

在where后面的列建立索引才会加快查询速度。

pages<---索引(属性)<----查数据。


1.4、索引分类


  • 主键索引

  • 普通索引

  • 唯一索引


1.5、添加索引


#创建索引

alter table test add index index_name(name);

#创建索引

create index index_name on test(name);

#查看索引

desc table;

#查看索引

show index from table;

#删除索引

alter table test drop key index_name;

#添加主键索引(略)

#添加唯一性索引

alter table student add unique key uni_xxx(xxx);

#查看表中数据行数

select count(*) from city;

#查看去重数据行数

select count(distinct name) from city;


1.6、前缀索引和联合索引


  • 前缀索引

    根据字段的前N个字符建立索引

alter table test add index idx_name(name(10));

避免对大列建索引

如果有,就使用前缀索引

  • 联合索引

    多个字段建立一个索引

    例:

    where a.女生 and b.身高 and c.体重 and d.身材好

    index(a,b,c)

    特点:前缀生效特性

    a,ab,ac,abc,abcd 可以走索引或部分走索引

    b bc bcd cd c d ba ... 不走索引

原则:把最常用来做为条件查询的列放在最前面

#创建people表

mysql> use test;

Database changed

mysql> show tables;

Empty set (0.00 sec)

mysql> create table people (id int,name varchar(20),age tinyint,money int,gender enum('m','f'));

 

#创建联合索引

mysql> alter table people add index idx_gam(gender,age,money);

Query OK, 0 rows affected (0.00 sec)

Records: 0  Duplicates: 0  Warnings: 0


2、explain详解



2.1、explain命令使用方法


mysql> explain select name,countrycode from world.city where id=1;


2.2、explain命令应用


查询数据方式:

  1. 全表扫描

    1)在explain语句结果中type为ALL

    2)什么时候出现全表扫描?

     2.1 业务确实要获取所有数据

     2.2 不走索引导致的全表扫描

       2.2.1 没索引

       2.2.2 索引创建有问题

             2.2.3 语句有问题

    生产中,mysql在使用全表扫描时的性能是极其差的,所以MySQL尽量避免出现全表扫描

  2. 索引扫描

2.1 常见的索引扫描类型:

1)index

2)range

3)ref

4)eq_ref

5)const

6)system

7)null

从上到下,性能从最差到最好,我们认为至少要达到range级别

  • index:Full Index Scan,index与ALL区别为index类型只遍历索引树。

  • range:索引范围扫描,对索引的扫描开始于某一点,返回匹配值域的行。显而易见的索引范围扫描是带有between或者where子句里带有<,>查询。

mysql> alter table city add index idx_city(population);

Query OK, 0 rows affected (0.03 sec)

Records: 0  Duplicates: 0  Warnings: 0

 

mysql> explain select * from city where population>3000000;

+----+-------------+-------+-------+---------------+----------+---------+------+------+-----------------------+

| id | select_type | table | type  | possible_keys | key      | key_len | ref  | rows | Extra                 |

+----+-------------+-------+-------+---------------+----------+---------+------+------+-----------------------+

|  1 | SIMPLE      | city  | range | idx_city      | idx_city | 4       | NULL |   46 | Using index condition |

+----+-------------+-------+-------+---------------+----------+---------+------+------+-----------------------+

1 row in set (0.00 sec)

  • ref:使用非唯一索引扫描或者唯一索引的前缀扫描,返回匹配某个单独值的记录行。

mysql> explain select * from city where countrycode in ('CHN','USA');

+----+-------------+-------+-------+---------------+-------------+---------+------+------+-----------------------+

| id | select_type | table | type  | possible_keys | key         | key_len | ref  | rows | Extra                 |

+----+-------------+-------+-------+---------------+-------------+---------+------+------+-----------------------+

|  1 | SIMPLE      | city  | range | CountryCode   | CountryCode | 3       | NULL |  637 | Using index condition |

+----+-------------+-------+-------+---------------+-------------+---------+------+------+-----------------------+

1 row in set (0.00 sec)

 

mysql> explain select * from city where countrycode='CHA';

+----+-------------+-------+------+---------------+-------------+---------+-------+------+-----------------------+

| id | select_type | table | type | possible_keys | key         | key_len | ref   | rows | Extra                 |

+----+-------------+-------+------+---------------+-------------+---------+-------+------+-----------------------+

|  1 | SIMPLE      | city  | ref  | CountryCode   | CountryCode | 3       | const |    1 | Using index condition |

+----+-------------+-------+------+---------------+-------------+---------+-------+------+-----------------------+

1 row in set (0.00 sec)

 

mysql> explain select * from city where countrycode='CHA' union all select * from city where countrycode='USA';

+----+--------------+------------+------+---------------+-------------+---------+-------+------+-----------------------+

| id | select_type  | table      | type | possible_keys | key         | key_len | ref   | rows | Extra                 |

+----+--------------+------------+------+---------------+-------------+---------+-------+------+-----------------------+

|  1 | PRIMARY      | city       | ref  | CountryCode   | CountryCode | 3       | const |    1 | Using index condition |

|  2 | UNION        | city       | ref  | CountryCode   | CountryCode | 3       | const |  274 | Using index condition |

| NULL | UNION RESULT | <union1,2> | ALL  | NULL          | NULL        | NULL    | NULL  | NULL | Using temporary       |

+----+--------------+------------+------+---------------+-------------+---------+-------+------+-----------------------+

3 rows in set (0.00 sec)

  • eq_ref:类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件A

join B

on A.sid=B.sid

  • const、system:当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。

    如将主键置于where列表中,MySQL就能将该查询转换为一个常量

mysql> explain select * from city where id=1000;

+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+

| id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra |

+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+

|  1 | SIMPLE      | city  | const | PRIMARY       | PRIMARY | 4       | const |    1 | NULL  |

+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+

1 row in set (0.00 sec)

  • NULL:MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。

mysql> explain select * from city where id=10000000000000000000;

+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+

| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra                                               |

+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+

|  1 | SIMPLE      | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | Impossible WHERE noticed after reading const tables |

+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+

1 row in set (0.00 sec)

 

  • Extra(扩展)

    Using temporary

    Using filesort 使用了默认的文件排序(如果使用了索引,会避免这类排序)

    Using join buffer

    如果出现Using filesort请检查order by ,group by ,distinct,join 条件列上没有索引

mysql> explain select * from city where countrycode='CHN' order by population;

+----+-------------+-------+------+---------------+-------------+---------+-------+------+----------------------------------------------------+

| id | select_type | table | type | possible_keys | key         | key_len | ref   | rows | Extra                                              |

+----+-------------+-------+------+---------------+-------------+---------+-------+------+----------------------------------------------------+

|  1 | SIMPLE      | city  | ref  | CountryCode   | CountryCode | 3       | const |  363 | Using index condition; Using where; Using filesort |

+----+-------------+-------+------+---------------+-------------+---------+-------+------+----------------------------------------------------+

1 row in set (0.00 sec)

当order by语句中出现Using filesort,那就尽量让排序值在where条件中出现

mysql> explain select * from city where population>3000000000000 order by population;

+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+

| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra                                               |

+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+

|  1 | SIMPLE      | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | Impossible WHERE noticed after reading const tables |

+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+

mysql> select * from city where population>3000000000000 order by population;

Empty set (0.00 sec)

 

mysql> select * from city where population>2870300 order by population;

+------+---------------------+-------------+---------------------+------------+

| ID   | Name                | CountryCode | District            | Population |

+------+---------------------+-------------+---------------------+------------+

| 1029 | Ahmedabad           | IND         | Gujarat             |    2876710 |

|  653 | Madrid              | ESP         | Madrid              |    2879052 |

| 3795 | Chicago             | USA         | Illinois            |    2896016 |

| 2485 | Casablanca          | MAR         | Casablanca          |    2940623 |

| 1028 | Hyderabad           | IND         | Andhra Pradesh      |    2964638 |

|   69 | Buenos Aires        | ARG         | Distrito Federal    |    2982146 |

| 3358 | Ankara              | TUR         | Ankara              |    3038159 |

|  130 | Sydney              | AUS         | New South Wales     |    3276207 |

| 3173 | Riyadh              | SAU         | Riyadh              |    3324000 |

|  609 | Alexandria          | EGY         | Aleksandria         |    3328196 |

| 1533 | Jokohama [Yokohama] | JPN         | Kanagawa            |    3339594 |

| 1898 | Chengdu             | CHN         | Sichuan             |    3361500 |

| 2710 | Rangoon (Yangon)    | MMR         | Rangoon [Yangon]    |    3361700 |

| 3068 | Berlin              | DEU         | Berliini            |    3386667 |

|  150 | Dhaka               | BGD         | Dhaka               |    3612850 |

| 3794 | Los Angeles         | USA         | California          |    3694820 |

| 2332 | Pusan               | KOR         | Pusan               |    3804522 |

| 1027 | Chennai (Madras)    | IND         | Tamil Nadu          |    3841396 |

| 3769 | Ho Chi Minh City    | VNM         | Ho Chi Minh City    |    3980000 |

| 3208 | Singapore           | SGP         | –                   |    4017733 |

| 1897 | Kanton [Guangzhou]  | CHN         | Guangdong           |    4256300 |

| 1896 | Shenyang            | CHN         | Liaoning            |    4265200 |

| 1895 | Harbin              | CHN         | Heilongjiang        |    4289800 |

| 1365 | Baghdad             | IRQ         | Baghdad             |    4336000 |

| 1894 | Wuhan               | CHN         | Hubei               |    4344600 |

| 1026 | Calcutta [Kolkata]  | IND         | West Bengali        |    4399819 |

| 3581 | St Petersburg       | RUS         | Pietari             |    4694000 |

|  554 | Santiago de Chile   | CHL         | Santiago            |    4703954 |

| 2823 | Lahore              | PAK         | Punjab              |    5063499 |

| 2298 | Kinshasa            | COD         | Kinshasa            |    5064000 |

| 1893 | Tianjin             | CHN         | Tianjin             |    5286800 |

|  207 | Rio de Janeiro      | BRA         | Rio de Janeiro      |    5598953 |

| 2257 | Santafé de Bogotá   | COL         | Santafé de Bogotá   |    6260862 |

| 3320 | Bangkok             | THA         | Bangkok             |    6320174 |

| 1892 | Chongqing           | CHN         | Chongqing           |    6351600 |

| 2890 | Lima                | PER         | Lima                |    6464693 |

| 1380 | Teheran             | IRN         | Teheran             |    6758845 |

|  608 | Cairo               | EGY         | Kairo               |    6789479 |

| 1025 | Delhi               | IND         | Delhi               |    7206704 |

|  456 | London              | GBR         | England             |    7285000 |

| 1891 | Peking              | CHN         | Peking              |    7472000 |

| 1532 | Tokyo               | JPN         | Tokyo-to            |    7980230 |

| 3793 | New York            | USA         | New York            |    8008278 |

| 3580 | Moscow              | RUS         | Moscow (City)       |    8389200 |

| 2515 | Ciudad de México    | MEX         | Distrito Federal    |    8591309 |

| 3357 | Istanbul            | TUR         | Istanbul            |    8787958 |

| 2822 | Karachi             | PAK         | Sindh               |    9269265 |

|  939 | Jakarta             | IDN         | Jakarta Raya        |    9604900 |

| 1890 | Shanghai            | CHN         | Shanghai            |    9696300 |

|  206 | São Paulo           | BRA         | São Paulo           |    9968485 |

| 2331 | Seoul               | KOR         | Seoul               |    9981619 |

| 1024 | Mumbai (Bombay)     | IND         | Maharashtra         |   10500000 |

+------+---------------------+-------------+---------------------+------------+

52 rows in set (0.00 sec)

key_len: 越小越好

前缀索引去控制

rows: 越小越好


3、建立索引的原则(规范)


为了使索引的使用效率更高,在创建索引时,必须考虑在哪些字段上创建索引和创建什么类型的索引。

那么索引设计原则又是怎样的?


3.1、选择唯一性索引


唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。

主键索引和唯一键索引,在查询中使用是效率最高的。

mysql> select count(*) from city;

+----------+

| count(*) |

+----------+

|     4079 |

+----------+

1 row in set (0.00 sec)

 

mysql> select count(distinct countrycode) from city;

+-----------------------------+

| count(distinct countrycode) |

+-----------------------------+

|                         232 |

+-----------------------------+

1 row in set (0.00 sec)

 

mysql> select count(distinct countrycode,population) from city;

+----------------------------------------+

| count(distinct countrycode,population) |

+----------------------------------------+

|                                   4052 |

+----------------------------------------+

1 row in set (0.00 sec)

注意:如果重复值较多,可以考虑采用联合索引


3.2、为经常需要排序、分组和联合操作的字段建立索引


例如:

经常需要ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段,排序操作会浪费很多时间。

如果为其建立索引,可以有效地避免排序操作


3.3、为常作为查询条件的字段建立索引


如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的查询速度。

因此,为这样的字段建立索引,可以提高整个表的查询速度。

  • 经常查询

  • 列值的重复值少

注:如果经常作为条件的列,重复值特别多,可以建立联合索引


3.4、尽量使用前缀来索引


如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT和BLOG类型的字段,进行全文检索

会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度。


3.5、限制索引的数目


索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。

修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间。


3.6、删除不再使用或者很少使用的索引


表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理

员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。


4、重点关注



4.1、没有查询条件,或者查询条件没有建立索引


#全表扫描

select * from table;

select  * from tab where 1=1;

在业务数据库中,特别是数据量比较大的表,是没有全表扫描这种需求。

1)对用户查看是非常痛苦的。

2)对服务器来讲毁灭性的。

3)SQL改写成以下语句:

#情况1

#全表扫描

mysql> select * from city;

#需要在price列上建立索引

mysql> select * from city order by id limit 10;

#情况2

#name列没有索引

select * from table where name='zhangsan';

1、换成有索引的列作为查询条件

2、将name列建立索引


4.2、查询结果集是原表中的大部分数据,应该是25%以上


mysql> explain select * from city where population>30000 order by population;

+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------+

| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra                       |

+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------+

|  1 | SIMPLE      | city  | ALL  | idx_city      | NULL | NULL    | NULL | 4188 | Using where; Using filesort |

+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------+

1 row in set (0.00 sec)

  • 如果业务允许,可以使用limit控制。

  • 结合业务判断,有没有更好的方式。如果没有更好的改写方案就尽量不要在mysql存放这个数据了,放到redis里面。


4.3、索引本身失效,统计数据不真实


索引有自我维护的能力。

对于表内容变化比较频繁的情况下,有可能会出现索引失效。

重建索引就可以解决


4.4、查询条件使用函数在索引列上或者对索引列进行运算,运算包括(+,-,*等)


#例子

错误的例子:select * from test where id-1=9;

正确的例子:select * from test where id=10;


4.5、隐式转换导致索引失效.这一点应当引起重视.也是开发中经常会犯的错误


mysql> create table test (id int ,name varchar(20),telnum varchar(10));

mysql> insert into test values(1,'zs','110'),(2,'l4',120),(3,'w5',119),(4,'z4',112);

mysql> explain select * from test where telnum=120;

mysql> alter table test add index idx_tel(telnum);

mysql> explain select * from test where telnum=120;

mysql> explain select * from test where telnum=120;

mysql> explain select * from test where telnum='120';


4.6、<> ,not in 不走索引


mysql> select * from tab where telnum <> '1555555';

mysql> explain select * from tab where telnum <> '1555555';

单独的>,<,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'


4.7、like "%_" 百分号在最前面不走


#走range索引扫描

EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '31%';

#不走索引

EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '%110';

%linux%类的搜索需求,可以使用Elasticsearch -------> ELK


4.8、单独引用联合索引里非第一位置的索引列


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

#走索引的情况测试

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';