MySQL 基础优化

执行计划

1 分析
1.1 什么是执行计划?

select * from t1 where name='zs';
分析的是优化器按照内置的cost计算算法,最终选择后的执行计划。
cost?
代价,成本。
对于计算机来讲 ,代价是什么?
IO ,CPU , MEM

1.2 查看执行计划

mysql> explain select * from world.city;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | city  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 4046 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.04 sec)

mysql> desc select * from world.city;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | city  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 4046 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.01 sec)

1.3 执行计划显示结果的认识

table    : 此次查询涉及到的表
type     : 查询类型:全表扫描,索引扫描    
possible_keys  : 可能用到的索引 
key     : 最后选择的索引
key_len : 索引覆盖长度
rows    : 此次查询需要扫描的行数
Extra   : 额外的信息

1.4 输出信息介绍
1.4.1 table

此次查询涉及到的表,针对一个查询中多个表时,精确到问题表
desc select country.name,city.name 
from world.city 
join country on city.countrycode
=country.code where city.population='CHN';
mysql> desc select country.name,city.name from world.city join country on city.countrycode=country.code where city.population='CHN';
+----+-------------+---------+------------+--------+---------------+---------+---------+------------------------+------+----------+-------------+
| id | select_type | table   | partitions | type   | possible_keys | key     | key_len | ref                    | rows | filtered | Extra       |
+----+-------------+---------+------------+--------+---------------+---------+---------+------------------------+------+----------+-------------+
|  1 | SIMPLE      | city    | NULL       | ALL    | CountryCode   | NULL    | NULL    | NULL                   | 4046 |    10.00 | Using where |
|  1 | SIMPLE      | country | NULL       | eq_ref | PRIMARY       | PRIMARY | 12      | world.city.CountryCode |    1 |   100.00 | NULL        |
+----+-------------+---------+------------+--------+---------------+---------+---------+------------------------+------+----------+-------------+
2 rows in set, 1 warning (0.05 sec)

mysql> alter table city add index idx(population);
Query OK, 0 rows affected (0.13 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> desc select country.name,city.name from world.city join country on city.countrycode=country.code where city.population='CHN';
+----+-------------+---------+------------+--------+-----------------+---------+---------+------------------------+------+----------+-------+
| id | select_type | table   | partitions | type   | possible_keys   | key     | key_len | ref                    | rows | filtered | Extra |
+----+-------------+---------+------------+--------+-----------------+---------+---------+------------------------+------+----------+-------+
|  1 | SIMPLE      | city    | NULL       | ref    | CountryCode,idx | idx     | 4       | const                  |    1 |   100.00 | NULL  |
|  1 | SIMPLE      | country | NULL       | eq_ref | PRIMARY         | PRIMARY | 12      | world.city.CountryCode |    1 |   100.00 | NULL  |
+----+-------------+---------+------------+--------+-----------------+---------+---------+------------------------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)

1.4.2 type 查询类型

(1)ALL: 全表扫描:不用任何的索引。

例如:
desc select * from city;
desc select * from city where 1=1;
desc select * from city where countrycode like '%ch%';
desc select * from city where countrycode not in ('CHN','USA');
desc select * from city where countrycode != 'CH%';

索引扫描:index < range < ref < eq_ref < const(system) 性能
mysql> desc select * from city;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | city  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 4046 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> desc select * from city where 1=1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | city  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 4046 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.03 sec)

mysql> desc select * from city where countrycode like '%ch%';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | city  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 4046 |    11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> desc select * from city where countrycode not in ('CHN','USA');
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | city  | NULL       | ALL  | CountryCode   | NULL | NULL    | NULL | 4046 |    85.07 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.05 sec)

mysql> desc select * from city where countrycode != 'CH%';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | city  | NULL       | ALL  | CountryCode   | NULL | NULL    | NULL | 4046 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

(2)index: 全索引扫描

mysql> desc select countrycode from world.city;
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | city  | NULL       | index | NULL          | CountryCode | 12      | NULL | 4046 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

(3)range: 索引范围查询:> < >= <= like in or between and

desc select * from city where id<10;
desc select * from city where countrycode like 'CH%';

desc select * from city where countrycode in ('CH%','USA');
------> 改写为 union all
select * from city where countrycode='CH%'
union all
select * from city where countrycode='USA'; # 怎么写????
mysql> desc select * from city where id<10;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | city  | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL |    9 |   100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> desc select * from city where countrycode like 'CH%';
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | city  | NULL       | range | CountryCode   | CountryCode | 12      | NULL |  397 |   100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.01 sec)

mysql> desc select * from city where countrycode in ('CH%','USA');
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | city  | NULL       | range | CountryCode   | CountryCode | 12      | NULL |  275 |   100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
特殊情况:查询条件为主键时
desc select * from city where id != 10;
desc select * from city where id not in (10,20);
mysql> desc select * from city where id != 10;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | city  | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL | 2032 |   100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> desc select * from city where id not in (10,20);
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | city  | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL | 2041 |   100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

(4)ref: 辅助索引的等值查询

desc select * from city where countrycode='CHN';
mysql> desc select * from city where countrycode='CHN';
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key         | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | city  | NULL       | ref  | CountryCode   | CountryCode | 12      | const |  363 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

(5)eq_ref: 多表链接中,非驱动表链接条件是主键或唯一键。

A join B on A.xx=B.yy

desc
select country.name,city.name from world.city join country on city.countrycode=country.code where city.population='CHN';
mysql> desc select country.name,city.name from world.city join country on city.countrycode=country.code where city.population='CHN';
+----+-------------+---------+------------+--------+-----------------+---------+---------+------------------------+------+----------+-------+
| id | select_type | table   | partitions | type   | possible_keys   | key     | key_len | ref                    | rows | filtered | Extra |
+----+-------------+---------+------------+--------+-----------------+---------+---------+------------------------+------+----------+-------+
|  1 | SIMPLE      | city    | NULL       | ref    | CountryCode,idx | idx     | 4       | const                  |    1 |   100.00 | NULL  |
|  1 | SIMPLE      | country | NULL       | eq_ref | PRIMARY         | PRIMARY | 12      | world.city.CountryCode |    1 |   100.00 | NULL  |
+----+-------------+---------+------------+--------+-----------------+---------+---------+------------------------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)

mysql> desc country;
+----------------+---------------------------------------------------------------------------------------+------+-----+---------+-------+
| Field          | Type                                                                                  | Null | Key | Default | Extra |
+----------------+---------------------------------------------------------------------------------------+------+-----+---------+-------+
| Code           | char(3)                                                                               | NO   | PRI |         |       |
| Name           | char(52)                                                                              | NO   |     |         |       |
| Continent      | enum('Asia','Europe','North America','Africa','Oceania','Antarctica','South America') | NO   |     | Asia    |       |
| Region         | char(26)                                                                              | NO   |     |         |       |
| SurfaceArea    | decimal(10,2)                                                                         | NO   |     | 0.00    |       |
| IndepYear      | smallint(6)                                                                           | YES  |     | NULL    |       |
| Population     | int(11)                                                                               | NO   |     | 0       |       |
| LifeExpectancy | decimal(3,1)                                                                          | YES  |     | NULL    |       |
| GNP            | decimal(10,2)                                                                         | YES  |     | NULL    |       |
| GNPOld         | decimal(10,2)                                                                         | YES  |     | NULL    |       |
| LocalName      | char(45)                                                                              | NO   |     |         |       |
| GovernmentForm | char(45)                                                                              | NO   |     |         |       |
| HeadOfState    | char(60)                                                                              | YES  |     | NULL    |       |
| Capital        | int(11)                                                                               | YES  |     | NULL    |       |
| Code2          | char(2)                                                                               | NO   |     |         |       |
+----------------+---------------------------------------------------------------------------------------+------+-----+---------+-------+
15 rows in set (0.00 sec)

(6)const(system): 聚簇索引等值查询

desc select * from city where id=10;
mysql> desc select * from city where id=10;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | city  | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

1.4.3 possible_keys,key

possible_keys    :可能会走的索引,所有和此次查询有关的索引。
key          :此次查询选择的索引。

1.4.4 key_len 联合索引覆盖长度
(1)介绍:

对于联合索引index(a,b,c), 我们希望将来的查询语句,对于联合索引应用越充分越好。
key_len, 可以帮助我们判断,此次查询,走了联合索引的几部分。
例如:    idx(a,b,c) ----> a ab abc
全部覆盖:
select * from t1 where a= and b= and c= 
select * from t1 where a in and b in and c in 
select * from t1 where b= and c= and a= 
select * from t1 where a and b order by c
部分覆盖:
select * from t1 where a= and b= 
select * from t1 where a=
select * from t1 where a= and c= 
select * from t1 where a= and b> < >= like and c= 
select xxx from t1 where a order by c
不覆盖:
bc
b
c

(2)key_len的计算:index(a,b,c)

假设,某条查询可以完全覆盖三列联合索引。
例如:
select * from t1 where a= and b= and c= key_len= a长度? + b长度? + c长度? 长度指的是什么? 长度受到: 数据类型,字符集 影响 长度指的是,列的最大储值字节长度 数字: not null 没有not null tinyint 1 1+1 int 4 4+1 bigint 8 8+1 key_len : a int not null -----> 4 a int -----> 5 字符:utf8 -----> 一个字符最大占3个字节 not null 没有 not null char(10) 3*10 3*10+1 varchar(10) 3*10+2 3*10+2+1 key_len : b char(10) not null 30 b char(11) 31 c varchar(10) not null 32 c varchar(10) 33 create table t1 ( a int not null, 4 b int , 5 c char(10) not null, 40 d varchar(10) 43 )charset = utf8mb4; index(a,b,c,d) 问:查询中完全覆盖到4列索引,key_len是多少?------》92 mysql> use test; mysql> create table t1 ( -> a int not null, -> b int , -> c char(10) not null, -> d varchar(10) -> )charset = utf8mb4; mysql> desc t1; mysql> alter table t1 add index idx(a,b,c,d); mysql> desc select * from t1 where a=1 and b=1 and c='a' and d='a'; mysql> desc select * from t1 where a=1 and b=1 and c='a' ; mysql> desc select * from t1 where a=1 and b=1 ; mysql> desc select * from t1 where a=1 ;

1.4.5 extra

using filesort: 表示此次查询使用到了文件排序,说明在查询中的排序操作:order by group by distinct ..

desc
select * from city where countrycode='CHN' order by population; show index from city; alter table city add index idx_c_p(countrycode,population); show index from city; desc select * from city where countrycode='CHN' order by population;
mysql> desc select * from city where countrycode='CHN' order by population;
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key         | key_len | ref   | rows | filtered | Extra                                 |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+---------------------------------------+
|  1 | SIMPLE      | city  | NULL       | ref  | CountryCode   | CountryCode | 12      | const |  363 |   100.00 | Using index condition; Using filesort |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+---------------------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> show index from city;
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name    | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| city  |          0 | PRIMARY     |            1 | ID          | A         |        4046 |     NULL | NULL   |      | BTREE      |         |               |
| city  |          1 | CountryCode |            1 | CountryCode | A         |         232 |     NULL | NULL   |      | BTREE      |         |               |
| city  |          1 | idx         |            1 | Population  | A         |        3897 |     NULL | NULL   |      | BTREE      |         |               |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3 rows in set (0.00 sec)

mysql> alter table city add index idx_c_p(countrycode,population);
Query OK, 0 rows affected (0.07 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> show index from city;
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name    | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| city  |          0 | PRIMARY     |            1 | ID          | A         |        4046 |     NULL | NULL   |      | BTREE      |         |               |
| city  |          1 | CountryCode |            1 | CountryCode | A         |         232 |     NULL | NULL   |      | BTREE      |         |               |
| city  |          1 | idx         |            1 | Population  | A         |        3897 |     NULL | NULL   |      | BTREE      |         |               |
| city  |          1 | idx_c_p     |            1 | CountryCode | A         |         232 |     NULL | NULL   |      | BTREE      |         |               |
| city  |          1 | idx_c_p     |            2 | Population  | A         |        4046 |     NULL | NULL   |      | BTREE      |         |               |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
5 rows in set (0.00 sec)

mysql> desc select * from city where countrycode='CHN' order by population;
+----+-------------+-------+------------+------+---------------------+---------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys       | key     | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+-------+------------+------+---------------------+---------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | city  | NULL       | ref  | CountryCode,idx_c_p | idx_c_p | 12      | const |  363 |   100.00 | Using index condition |
+----+-------------+-------+------------+------+---------------------+---------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

2. 索引应用规范

2.1 建立索引的原则(DBA运维规范)
2.1.0 说明

为了使索引的使用效率更高,在创建索引时,必须考虑在哪些字段上创建索引和创建什么类型的索引。那么索引设计原则又是怎样的?

2.1.1 (必须的)建表时一定要有主键,一般是个无关列

略,回顾一下,聚簇索引结构。

2.1.2 选择唯一性索引

唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。
例如,学生表中学号是具有唯一性的字段。为该字段建立唯一性索引可以很快的确定某个学生的信息。
如果使用姓名的话,可能存在同名现象,从而降低查询速度。
优化方案:
(1)如果非得使用重复值较多的列作为查询条件(例如:男女),可以将表逻辑拆分
(2)可以将此列和其他的查询类,做联合索引
select count(*) from world.city;
select count(distinct countrycode) from world.city;
select count(distinct countrycode,population ) from world.city;
mysql> select count(*) from world.city;
+----------+
| count(*) |
+----------+
|     4079 |
+----------+
1 row in set (0.02 sec)

mysql> select count(distinct countrycode) from world.city;
+-----------------------------+
| count(distinct countrycode) |
+-----------------------------+
|                         232 |
+-----------------------------+
1 row in set (0.00 sec)

mysql> select count(distinct countrycode,population ) from world.city;
+-----------------------------------------+
| count(distinct countrycode,population ) |
+-----------------------------------------+
|                                    4052 |
+-----------------------------------------+
1 row in set (0.01 sec)

2.1.3 (必须的)为经常需要where, order by , group by , join on 等操作的字段,排序操作会浪费很多时间。

where A B C ----》 A B C
in 
where A group by B order by C
A,B,C
如果为其建立索引,优化查询
注:如果经常作为条件的列,重复值特别多,可以建立联合索引。

2.1.4 尽量使用前缀来索引

如果索引字段的值很长,最好使用值的前缀来索引。

2.1.5 限制索引的数目

索引的数目不是越多越好。
可能会产生的问题:
(1) 每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。
(2) 修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间。
(3) 优化器的负担会很重,有可能会影响到优化器的选择.
percona-toolkit中有个工具,专门分析索引是否有用

2.1.6 删除不再使用或者很少使用的索引(percona toolkit)

pt-duplicate-key-checker
5.7以前:
mysql> use mysql
mysql> show tables;
+---------------------------+
| Tables_in_mysql           |
+---------------------------+
| innodb_index_stats        |

5.7+:
mysql> use sys
mysql> show tables;
+-----------------------------------------------+
| Tables_in_sys                                 |
+-----------------------------------------------+
| schema_unused_indexes                         |

表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。
数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。

2.1.7 大表加索引,要在业务不繁忙期间操作
2.1.8 尽量少在经常更新值的列上建索引
2.1.9 建索引原则

(1) 必须要有主键,如果没有可以做为主键条件的列,创建无关列
(2) 经常做为where条件列 order by group by join on, distinct 的条件(业务:产品功能+用户行为)
(3) 最好使用唯一值多的列作为索引,如果索引列重复值较多,可以考虑使用联合索引
(4) 列值长度较长的索引列,我们建议使用前缀索引.
(5) 降低索引条目,一方面不要创建没用索引,不常使用的索引清理,percona toolkit(xxxxx)
(6) 索引维护要避开业务繁忙期

2.2 不走索引的情况(开发规范)
2.2.1 没有查询条件,或者查询条件没有建立索引

select * from tab; 全表扫描。
select * from tab where 1=1;
在业务数据库中,特别是数据量比较大的表。
是没有全表扫描这种需求。
1、对用户查看是非常痛苦的。
2、对服务器来讲毁灭性的。
(1select * from tab;
SQL改写成以下语句:
select * from tab order by price limit 10 ; 需要在price列上建立索引
(2select * from tab where name='zhangsan' name列没有索引
改:
1、换成有索引的列作为查询条件
2、将name列建立索引

2.2.2 查询结果集是原表中的大部分数据,应该是15-30%以上。

查询的结果集,超过了总数行数25%,优化器觉得就没有必要走索引了。
与数据库的预读能力有关,以及一些参数有关。彩蛋。。。。。

假如:tab表 id,name id:1-100w ,id列有(辅助)索引
select * from tab where id>500000;
如果业务允许,可以使用limit控制。

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

 

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

索引和表有自我维护的能力。
对于表内容变化比较频繁的情况下,统计信息不准确,过旧。有可能会出现索引失效。
一般是删除重建

现象:
有一条select语句平常查询时很快,突然有一天很慢,会是什么原因
select? --->统计数据不真实,导致索引失效。
DML   ? --->锁冲突
mysql> use mysql;
mysql> show tables;
+---------------------------+
| Tables_in_mysql |
+---------------------------+
| innodb_index_stats |
| innodb_table_stats |
这两个表存放索引和表的状态信息,不是实时更新的
mysql> select * from innodb_index_stats;
mysql> select * from innodb_table_stats;
mysql> optimize table world.city;(实时更新)
mysql> alter table world.city engine=innodb;

[root@db01 ~]$ cd /data/3306/mysql
innodb_index_stats.frm
innodb_index_stats.ibd
innodb_table_stats.frm
innodb_table_stats.ibd

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

 

例子:
错误的例子:select * from test where id-1=9;
正确的例子:select * from test where id=10;
算术运算
函数运算
子查询

mysql> desc select * from world.city where id=3000;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | city | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> desc select * from world.city where id-1=2999;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | city | NULL | ALL | NULL | NULL | NULL | NULL | 4046 | 100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

2.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>
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)
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)
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.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)
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'

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

EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '31%' 走range索引扫描
EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '%110' 不走索引
%linux%类的搜索需求,可以使用elasticsearch+mongodb 专门做搜索服务的数据库产品

3. 扩展:优化器针对索引的算法

自优化能力

3.1 MySQL索引的自优化-AHI(自适应HASH索引)

 

MySQL的InnoDB引擎,能够创建只有Btree。

AHI作用:自动评估“热”的内存索引page,生成HASH索引表。
帮助InnoDB快速读取索引页。加快索引读取的所读。
相当于索引的索引。

 

 

 

3.2 MySQL索引的自优化-Change buffer

比如insert,update,delete 一行数据。
对于聚簇索引会立即更新。
对于辅助索引,不是实时更新的。

在InnoDB 内存结构中,加入了insert buffer(会话),现在版本叫change buffer。
change buffer 功能是临时缓存辅助索引需要的数据更新。
当我们需要查询新insert的数据,会在内存中进行merge(合并)操作,此时辅助索引就是最新的。

优化器算法介绍;

 

show variables like '%switch%';
select @@optimizer_switch;

 

 

mysql> show variables like '%switch%'\G
*************************** 1. row ***************************
Variable_name: optimizer_switch
        Value: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=on,condition_fanout_filter=on,derived_merge=on
1 row in set (0.00 sec)

mysql> select @@optimizer_switch\G
*************************** 1. row ***************************
@@optimizer_switch: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=on,condition_fanout_filter=on,derived_merge=on
1 row in set (0.01 sec)
如何修改?
1. my.cnf
2. set global optimizer_switch='batched_key_access=on';
3. hints
SELECT /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY, f2_idx) */ f1
FROM t3 WHERE f1 > 30 AND f1 < 33;
SELECT /*+ BKA(t1) NO_BKA(t2) */ * FROM t1 INNER JOIN t2 WHERE ...;
SELECT /*+ NO_ICP(t1, t2) */ * FROM t1 INNER JOIN t2 WHERE ...;
SELECT /*+ SEMIJOIN(FIRSTMATCH, LOOSESCAN) */ * FROM t1 ...;
EXPLAIN SELECT /*+ NO_ICP(t1) */ * FROM t1 WHERE ...;
https://dev.mysql.com/doc/refman/5.7/en/optimizer-hints.html

3.3 ICP 索引下推

作用;
解决了,联合索引只能部分应用情况。
为了使减少没必要的数据页被扫描。
将不走所有的条件,在engine层取数据之前先做c二次过滤。
一些无关数据
https://mariadb.com/kb/en/multi-range-read-optimization/

 

index(a,b,c)
select * from t1 where a= and c=

在 server 先做a列过滤条件的索引优化,1-10
在 将c列的过滤下推到enigen层先做过滤,加载数据页 6-8

3.4 MRR muti range read 

mysql> set global optimizer_switch='mrr=on,mrr_cost_based=off';

辅助索引 --- 回表----》 聚簇索引
转换为
辅助索引 ---》sort id -- 回表--》聚簇索引

3.5 SNLJ

例子:
A join B on A.xx = b.yy where ....a.zz=XXXX
伪代码:
for each row in A matching range {
block
for each row in B {
A.xx = B.yy , send to client
}

}

以上例子,可以通过left join 强制驱动表。

 

3.6 BNLJ

在 A和B关联条件匹配时,不再一次一次进行循环。
而是采用一次性将驱动表的关联值和非驱动表匹配,一次性返回

主要优化了,CPU消耗,减少了IO次数

 

3.7 BKA

主要作用:使用来优化非驱动表的关联列有辅助索引。
BNL+MRR的功能
开启方式
mysql> set global optimizer_switch='mrr=on,mrr_cost_based=off';
mysql> set global optimizer_switch='batched_key_access=on';
重新登录生效。

 

 

posted @ 2020-08-02 08:55  丁海龙  阅读(129)  评论(0)    收藏  举报