从数据库的执行计划来谈谈SQL索引优化
本文分4个章节:1.分析SQL语句的执行流程。2.分析explain执行计划中每个列的含义。3.针对explain列的含义来进行索引优化,4.优化总结
文章目录
- 从数据库的执行计划来谈谈SQL索引优化
- 1.SQL语句的执行流程
- 2.执行计划解析
- 1. id 列
- 2. select_type 列
- 3. table 列
- 4. type 列
- 5. possible_keys 列
- 6. key 列
- 7. key_len 列
- 8. ref 列
- 9. rows 列
- 10. filtered 列
- 11. extra 列
- 3. 索引优化
- 1. 全值匹配
- 2. 最佳左前缀法则
- 3. 避免在索引列上做计算操作
- 4. 范围条件右边的列无法使用索引
- 5. 尽量使用覆盖索引
- 6. 范围条件查找能够命中索引
- 7. IS NOT NULL 无法使用索引
- 8. 模糊查询以通配符开头索引失效
- 9. 字符串类型不加单引号索引失效
- 10. or使用多数情况下索引会失效‘
- 11. 负向查询条件不能使用索引
- 12. 排序对索引的影响
- 13. 局部索引的使用
- 4. 索引优化总结
1.SQL语句的执行流程
首先了解一下MySQL的逻辑架构图:
通过图片可以大致将MySQL架构分为两个层来实现:Server层和存储引擎。
连接器:
执行SQL语句的第一步肯定是需要连接数据库。这部分负责用户的身份认证。
查询缓存(8.0移除):
该部分缓存了执行过的SELECT语句以及相应的结果集,并以key-value的方式缓存在内存中。
建立连接后,执行查询语句的时候首先会在这里验证sql语句是否执行过,如果命中了key,就直接会将value返回给客户端,如果没有就进入分析器中执行后续操作。
但是实际业务中会频繁发生缓存失效的问题,因为当你执行对表执行一个更新操作后,这个表上的所有查询缓存都会被清空,所以一般不推荐使用查询缓存。
在MySQL8.0版本中改功能被移除。
分析器:
该部分主要是分析SQL语句的作用是什么,大致分为以下两步:
- 词法分析:一条SQL语句有多个字符串组成,首先要提取关键字,select、insert、表名、字段名、查询条件等等。
- 语法分析校验sql是否符合MySQL的语法。
当分析完SQL语句之后,接着就需要对该语句进行优化。
优化器:
优化器的作用就是选择较优的执行方案来执行SQL语句,多表查询的关联顺序、是选择索引还是全表查询。
当经过优化器之后,该SQL语句具体如何执行基本就定下来了。
执行器:
执行SQL语句。
2.执行计划解析
通过explain来查看sql语句的执行计划,从而提升SQL查询语句的性能。
字段名 | 用途 |
---|---|
id | 每个select关键字都对应一个id |
select_type | select关键字对应的查询类型 |
table | 表名 |
partitions | 匹配的分区信息 |
type | 单表的访问方法 |
possible_keys | 可能用到的索引 |
key | 实际使用的索引 |
key_len | 实际使用到的索引长度 |
ref | 当使用索引列等值查询时,与索引列进行等值匹配的对象信息 |
rows | 预计需要读取的记录条数 |
filtered | 某个表经过条件过滤后剩余的记录条数百分比 |
Extra | 一些额外的信息 |
建表:
DROP TABLE IF EXISTS user;
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`name` varchar(45) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO user (`id`, `name`, `update_time`)
VALUES (1,'a','2017-12-22 15:27:18'), (2,'b','2017-12-22 15:27:18'), (3,'c','2017-12-22 15:27:18');
DROP TABLE IF EXISTS `gro`;
CREATE TABLE `gro` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `gro` (`id`, `name`) VALUES (1,'group1'),(2,'group2'),(3,'group3');
DROP TABLE IF EXISTS user_group;
CREATE TABLE `user_group` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`group_id` int(11) NOT NULL,
`remark` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_group_id` (`group_id`),
KEY `idx_user_group_id` (`user_id`,`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO user_group (`id`, `user_id`, `group_id`, `remark`)
VALUES (1,1,1,'bak1'), (2,2,2,'bak2'), (3,3,3,'bak3');
1. id 列
该列的值表示的是select的序列号,id 的顺序是按 SELECT 出现的顺序增长的。id列越大执行优先级越高,id 相同则从上往下执行,id 为 NULL 最后执行。
MySQL将 SELECT
查询分为简单查询 SIMPLE
和复杂查询 PRIMARY
。
复杂查询包括:简单子查询、派生表( FROM
语句中的子查询)、UNION
和 UNION ALL
查询。
简单查询:
复杂查询:
-
简单子查询:
注意:在查询过程中最好将子查询转换为连接查询,子查询会在查询过程中创建临时表,因此存在一个创建销毁表的过程,不过优化器一般会将子查询优化为连接查询。
没有优化:
select_type=subquery
就表示子查询中的第一个查询。优化:
优化器将子查询转化为连接查询,查询计划中两个id值是相同的:
-
派生表:
该查询首先会遍历gro表索引树,因为只查询了主键列故不需要查询表(type=index)。接着会对派生表进行全表扫描。select_type=derived
表示:派生表查询,既from字句中的子查询。 -
union和union all查询:
UNION
结果总是放在一个匿名临时表中,id为null最后执行。跟 UNION
对比,UNION ALL
无需为最终结果而去重,仅是单纯的将多个查询结果集中的记录合并成一个并返回给用户,所以不会使用到临时表,故没有 id
为 NULL
记录。如下所示:
2. select_type 列
每一个 SELECT
关键字的查询都定义了一个 select_type
属性,知道这个查询属性就能知道在整个查询语句中所扮演的角色。
1)SIMPLE
:简单查询。查询不包含子查询 和 UNION
。
2)PRIMARY
:复杂查询中最外层的SELECT
,可参照上面的 UNION
查询语句。
3)SUBQUERY
:包含的子查询语句无法转换为 semi-join
,并且为不相关子查询,查询优化器采用物化方案执行该子查询,该子查询的第一个 SELECT
就会 SUBQUERY
。该查询由于被物化,只需要执行一次
。如上面简单子查询中没有优化的查询。
4)DERIVED
:对于采用物化形式执行的包含派生表的查询,该派生表的对应的子查询为 DERIVED
。具体可以看上面的派生表例子。
5)UNION
:在 UNION
查询语句中的第二个和紧随其后的 SELECT
。
6)UNION RESULT
:MySQL选择使用临时表完成 UNION
查询的去重工作。
当 select_type
为这个值时,经常可以看到table的值是 <unionN,M>
,这说明匹配的 id 行 是这个集合的一部分。请看上面 UNION
查询示例。
这些是比较常见的值,还有MATERIALIZED
、DEPENDENT SUBQUERY
…
3. table 列
这个值可能是表名、表的别名或者一个未查询产生临时表的标识符,如派生表、子查询或集合。
当 FROM
子句中有子查询时,如果优化器采用的物化方式,table 列是 <derivenN>
格式,表示当前查询依赖 id=N
的查询,于是先执行 id=N
的查询。如上面的派生表查询。
当使用 UNION
查询时,UNION RESULT
的 table 列的值为 <UNION1,2>
,1和2表示参与 UNION
的 SELECT 的行 id。如上面的union查询。
4. type 列
这一列表示关联类型或访问类型,即MySQL决定如何查找表中的行,查找数据行记录的大概范围。依次从最优到最差分别为:null>system > const > eq_ref > ref > range > index > ALL 一般来说,得保证查询达到range级别,最好达到ref。
NULL:mysql能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。例如:在索引列中选取最小值,可以单独查找索引来完成,不需要在执行时访问表 mysql> explain select min(id) from user。
-
system/const
:表示使用主键索引或者唯一非空索引来作为查询条件并且匹配的是一个常量,因此该查询结果最多只有一行。system
是const的一种特例,出现这种情况表示表中只有一条记录。 -
eq_ref
:在连接查询时如果被驱动表是通过主键或者唯一二级索引等值匹配的方式进行访问,则对被驱动表访问方式就是eq_ref
。 如下所示:gro.id是主键。
-
ref
:相比较于eq_ref是使用唯一索引,ref使用的是普通索引或者唯一性索引的部分前缀,索引要和某个值比较,可能会有多个符合条件的记录。a. 简单查询,使用普通索引gro.name。
b. 关联查询,使用了user_group的联合索引idx_user_group_id的最左边前缀的user_id。
4.range
:使用索引获取范围区间的记录,通常出现在in,between,<,>,>=
等操作中。
-
index
:扫描全表索引(index
是从索引中读取的,而ALL
是从硬盘中读取)
-
ALL
:全表扫描,MySQL 需要从头到尾去查找表中所需要的行。通常情况下这需要增加索引来进行优化了。
5. possible_keys 列
possible_keys
列表示查询可能使用哪些索引来查找。
EXPLAIN
执行计划结果可能出现 possible_keys
列,而 key
显示 NULL
的情况,这种情况是因为表中数据不多,MySQL 会认为索引对此查询帮助不大,选择了全表查询。
如果 possible_keys
列为 NULL
,则没有相关的索引。在这种情况下,可以通过检查 WHERE
子句去分析下,看看是否可以创造一个适当的索引来提高查询性能,然后用 EXPLAIN
查看效果。
另外注意:不是这一列的值越多越好,使用索引过多,查询优化器计算时查询成本高,所以如果可能的话,尽量删除那些不用的索引。
6. key 列
key
列表示实际采用哪个索引来优化对该表的访问。
如果没有使用索引,则该列是 NULL。如果想强制 MySQL使用或忽视 possible_keys
列中的索引,在查询中使用 force index
、ignore index
。
7. key_len 列
key_len
列表示当查询优化器决定使用某一个索引查询时,该索引记录的最大长度。
计算规则如下:
-
字符串
char(n):n字节长度
varchar(n):2字节存储字符串长度,如果是utf-8,则长度 3n + 2
**注意:**该索引列可以存储
NULL
值,则key_len
比不可以存储NULL
值时多1个字节。比如:varchar(50),则实际占用的
key_len
长度是 3 * 50 + 2 = 152,如果该列允许存储NULL
,则key_len
长度是153。 -
数值类型
tinyint:1字节 smallint:2字节 int:4字节 bigint:8字节
-
时间类型
date:3字节 timestamp:4字节 datetime:8字节
索引最大长度是768字节,当字符串过长时,MySQL 会做一个类似左前缀索引的处理,将前半部分的字符提取出来做索引。
例1:
user_group
表中的联合索引 idx_user_group_id
由 user_id
和 group_id
两个int 列组成,并且每个 int 是 4 字节。
例2:
再看 user
表 name 字段是 varchar(45) 变长字符串类型,key_len
为138 等于 45 * 3 + 2 (变长字节) + 1字节(允许存储NULL值)
8. ref 列
ref
列显示了在 key
列记录的索引中,表查找值所用到的列或常量,常见的有:const
(常量),字段名
(例:user.id
)。
9. rows 列
ref
列显示了在 key
列记录的索引中,表查找值所用到的列或常量,常见的有:const
(常量),字段名
(例:user.id
)。
10. filtered 列
对於单表来说意义不大,主要用于连接查询中。
前文中也已提到 filtered
列,是一个百分比的值,对于连接查询来说,主要看驱动表
的 filtered
列的值 ,通过 rows * filtered/100
计算可以估算出被驱动表
还需要执行的查询次数。
11. extra 列
Extra
列提供了一些额外信息。这一列在 MySQL中提供的信息有几十个,这里仅列举一些常见的重要值如下:
-
Using index
:查询的列被索引覆盖,并且WHERE
筛选条件是索引的前导列,使用了索引性能高。一般是使用了覆盖索引(查询列都是索引列字段)。对于 INNODB 存储引擎来说,如果是辅助索引(非聚集索引)性能会有不少提高,并且也不需要回表查询。
-
Using where;Using where
:查询的列被索引覆盖,并且WHERE
筛选条件是索引列之一,但并不是索引的前导列,意味着无法直接通过索引查找来查询到符合条件的数据。还有一种情况是前导列但是条件为范围查询。首先需要把user_group中的group_id这个索引删除,如果没有删除那么
key=idx_group_id Extra=Using index
,因为检索条件是前导列。
没有删除
-
NULL
:查询的列未被索引覆盖,并且WHERE
筛选条件是用到了索引,但是部分字段未被索引覆盖,不是纯粹地用到了索引,也不是完全没用到索引。或者没有使用到额外条件来查询(查询的列没有完全覆盖索引)。
-
Using temporary
:MySQL中需要创建一张内部临时表来处理查询,一般出现这种情况就需要考虑优化了,首先是想到用索引来优化。
通常在许多执行包括DISTINCT、GROUP BY、ORDER BY等子句查询过程中,如果不能有效利用索引来完成查询,MySQL很有可能会寻求建立内部临时表来执行查询。
所以,执行计划中出现了
Using temporary
并不是个好兆头,因为建立与维护临时表要付出很大的成本的,要考虑使用索引
来优化改进。
-
Using filesort
:MySQL 会对结果使用一个外部索引排序,而不是按索引次序从表里读取行。此时 MySQL 会根据联接类型浏览所有符合条件的记录,并保存排序关键字和行指针,然后排序关键字并按顺序检索行信息。这种情况下一般也是要考虑使用索引来优化的。查询中需要使用
filesort
的方式进行排序的记录非常多,那么这个过长是很耗时的,想办法将使用文件排序
的执行方式改进为使用索引
进行排序。 -
Index merges
:通常显示为Using sort_union(...)
说明准备用Sort-Union
索引合并方式来查询;显示为Using union(...)
,说明准备用Union
索引合并方式查询;显示为Using intersect(...)
,说明准备使用Intersect
索引合并方式查询。
3. 索引优化
# 重建 `staff` 表
DROP TABLE `staff`;
CREATE TABLE `staff` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
`s_name` VARCHAR(24) NOT NULL DEFAULT '' COMMENT '花名',
`s_no` INT(4) NOT NULL DEFAULT 0 COMMENT '工号',
`work_age` int(11) NOT NULL DEFAULT '0' COMMENT '工龄',
`position` varchar(20) NOT NULL DEFAULT '' COMMENT '职位',
`arrival_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注', # 允许 NULL
PRIMARY KEY (`id`), # 主键
UNIQUE KEY idx_s_name (s_name), # 唯一索引
KEY idx_s_no (s_no), # 普通索引
KEY `idx_name_age_position` (`name`,`work_age`,`position`) USING BTREE # 联合索引
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='员工记录表';
# 初始化 `staff` 表数据
INSERT INTO staff(name,s_name,s_no,work_age,position,arrival_time) VALUES('zhangsan','zs',10,2,'manager',NOW());
INSERT INTO staff(name,s_name,s_no,work_age,position,arrival_time) VALUES('lisi','ls',11,3,'dev',NOW());
INSERT INTO staff(name,s_name,s_no,work_age,position,arrival_time) VALUES('wangwu','ww',12,8,'dev',NOW());
INSERT INTO staff(name,s_name,s_no,work_age,position,arrival_time) VALUES('zhangliu','zl',110,5,'dev',NOW());
INSERT INTO staff(name,s_name,s_no,work_age,position,arrival_time) VALUES('xiaosun','xs',111,5,'dev',NOW());
INSERT INTO staff(name,s_name,s_no,work_age,position,arrival_time) VALUES('donggua','dg',200,3,'dev',NOW());
创建一个staff表来实践。
其实中心思想就是:尽量通过索引来查数据,并且要覆盖索引,避免进行回表操作(需要在两次b+tree中取数据),并且避免过多的创建索引,存储插入数据的时候需要更多的时间去维护。尽量在两者中取得平衡。
1. 全值匹配
按索引顺序匹配使用。
EXPLAIN SELECT * FROM staff WHERE name= ‘zhangsan’;
type为ref,使用到了索引,效率高。
EXPLAIN SELECT * FROM staff WHERE name= ‘zhangsan’ AND work_age = 2;
按照联合索引的顺序来查,type=ref使用了索引。
2. 最佳左前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
EXPLAIN SELECT * FROM staff WHERE name = ‘zhangsan’ AND work_age = 3 AND position = ‘manager’;
EXPLAIN SELECT * FROM staff where position = ‘dev’ AND name = ‘zhangsan’ AND work_age = 2;
这条语句并没有按照索引顺序来查询,但是mysql优化器会自动优化,所以还是会正确的使用到索引。
以下执行都是全表扫描,type为ALL
,不符合最左前缀法则:
EXPLAIN SELECT * FROM staff WHERE work_age = 2 AND position =‘dev’;
3. 避免在索引列上做计算操作
索引上尽量避免做函数计算等操作,会导致索引失效而转向全表扫描。
WHERE
条件后面索引列使用函数:
4. 范围条件右边的列无法使用索引
EXPLAIN SELECT * FROM staff WHERE name= ‘zhangsan’ AND work_age > 2 AND position =‘dev’;
可以看到type=range
,并且key_len=78 (3*24+2+4)
只使用了name和work_age索引, 而 position 字段并没有用到索引(没有使用到BTree的索引去查询),只是从 name = 'zhangsan' AND work_age > 2
条件返回的结果集中,再过滤符合 position 字段条件的数据。
5. 尽量使用覆盖索引
覆盖索引:简单理解,只访问建了索引的列。减少使用 SELECT *
语句查询列。
使用了覆盖索引:
EXPLAIN SELECT name,work_age FROM staff WHERE name= ‘zhangsan’ AND work_age = 3;
可以看到Extra=Using index
,但是这种情况在生产环境中一般无法满足要求。
附一个曾经线上SQL的优化记录:
artist 表有几十万条的数据量,第一条执行的SQL没有索引直接查询,查询耗时 0.557
毫秒;第一次优化
新建 founded 字段作为普通索引,查询耗时 0.0224
毫秒;第二次优化
再次重建联合索引 founded_name,优化后查询耗时:0.0051
毫秒。因为使用了覆盖索引查询方式,基于此优化,SQL查询效率提升非常明显。
6. 范围条件查找能够命中索引
范围条件查询首先优化器会判断预估记录数和全表记录数的差距,如果差距不大那么即使where条件是通过索引来查询的,优化器可能会直接全表查询。
范围条件主要包括 <、<=、>、>=、between
等。
若条件中范围列有普通索引和主键索引同时存在, 优先使用主键索引:
EXPLAIN SELECT * FROM staff WHERE staff.s_no > 10 AND staff.id > 2;
key=primary
代表使用的索引为主键索引。
范围列可以用到索引,注意联合索引必须符合最左前缀法则,如果查询条件中有两个范围列则无法全用到索引,优化器会去选择:
explain select * from staff where staff.name != ‘zl’ AND s_no <12;
explain select * from staff where staff.name != ‘zl’ AND s_no >1;
该条语句直接type=ALL
是因为rows=6
和表中总记录数相同,优化器直接使用全表查询了。
若条件中范围查询和等值查询同时存在,优先匹配等值查询列的索引:
EXPLAIN SELECT * FROM staff WHERE staff.s_no > 10 AND staff.s_name = ‘zl’;、
7. IS NOT NULL 无法使用索引
索引列建议都使用 NOT NULL 约束
及默认值,单列索引不存 NULL 值,联合索引不存全部为 NULL 的值,如果列允许为 NULL,查询结果可能不符合预期。
staff 表中为 remark
字段新建普通索引:
ALTER TABLE staff ADD INDEX idx_remark (remark);
IS NULL
查询命中索引:
EXPLAIN SELECT * FROM staff WHERE staff.remark IS NULL;
给remark添加完索引后需要赋值,因为remark默认是null,这条语句会命中所有行,优化器就会进行全表查询。上图是赋值过后的结果。
IS NOT NULL
查询不会命中索引:
EXPLAIN SELECT * FROM staff WHERE staff.remark IS NOT NULL;
8. 模糊查询以通配符开头索引失效
like '%xx'
或 like '%xx%'
前导模糊查询不能命中索引:
EXPLAIN SELECT * from staff where name like ‘%zhang%’;
如何使用模拟查询才能命中索引?
-
like 'xx%'
**非前导模糊查询可以命中索引: **
EXPLAIN SELECT * FROM staff WHERE name LIKE ‘zhang%’;
-
使用覆盖索引,查询字段必须要建立覆盖索引字段
EXPLAIN SELECT name,work_age FROM staff WHERE name LIKE ‘%zhang%’;
联合索引是
idx_name_work_age_position
9. 字符串类型不加单引号索引失效
字符串的数据类型一定要将常量值使用单引号,这个在日常开发中要特别注意的,数据类型出现隐式转换的时候不会命中索引。
不加单引号索引失效
EXPLAIN SELECT * FROM staff WHERE name = 1;
name=1 类似于在该字段上做了一个函数运算,因此不会走索引的。
加单引号会命中索引:
EXPLAIN SELECT * FROM staff WHERE name = ‘zhangsan’;
10. or使用多数情况下索引会失效‘
EXPLAIN SELECT * FROM staff WHERE name=‘zhangsan’ OR work_age = 2;
尽管name和work_age是联合索引,但是work_ag列上并没有索引,因此会走全表扫描,存在全表扫描的情况下,就没必要在进行一次索引扫描。
OR 后面也使用索引列:
explain select * from staff where s_name=‘zs’ or s_no =10;
这里需要在staff表中插入新的数据,不然优化器可能会优化成全表搜索。
Using union
:表示使用or连接各个使用索引的条件时,该信息表示从处理结果获取并集。
如果执行计划 Extra
列出现了 Using sort_union(...)
的提示,说明准备使用 Sort-Union
索引合并的方式执行查询。如果出现了 Using intersect(...)
的提示,说明准备使用 Intersect
索引合并方式执行查询,如果出现了 Using union(...)
的提示 ,说明准备使用 Union
索引合并方式执行查询。括号中 ...
表示需要进行索引合并的索引名称
**使用UNION优化改进: **
explain select * from staff where s_name=‘zs’ union select * from staff where s_no =10;
使用 UNION
执行计划中出现了第三条记录,Extra
中出现 Using temporary
,说明 MySQL因为不能有效利用索引,建立了内部临时表来执行查询。当你在使用 DISTINCT 、GROUP BY、UNION
等子句中的查询过程中,都有可能会出现该扩展信息。
使用UNION ALL进一步优化:
explain select * from staff where s_name=‘zs’ union all select * from staff where s_no =10;
执行结果中不再出现内部临时表,具体用的时候结合实际需求来定是否使用。
11. 负向查询条件不能使用索引
负向查询条件包括:!=、<>、NOT IN、NOT EXISTS、NOT LIKE
等。
不会命中索引:
EXPLAIN SELECT * FROM staff WHERE s_no !=1 AND s_no != 2;
EXPLAIN SELECT * FROM staff WHERE s_no NOT IN (1,2);
使用IN优化,命中索引:
EXPLAIN SELECT * FROM staff WHERE s_no IN (11,12);
但是使用 IN
命中索引有个前提,是查询条件字段数据区分度要高,通常如:状态、类型、性别之类的字段。
12. 排序对索引的影响
ORDER BY
是经常用的语句,排序也遵循最左前缀列的原则。
查询所有列未命中索引:
EXPLAIN SELECT * FROM staff ORDER BY name,work_age;
覆盖索引查询可命中索引: 但要遵循最左前缀法则
可以看到一开始没有遵循最左前缀法则出现了Using filesort
,一看到这个我们就需要考虑对SQL语句进行优化了。
13. 局部索引的使用
局部索引,区别于最左列索引(顺序取索引中靠左的列的查询),它只取某列的一部分作为索引。
INNODB存储引擎下,一般是字符串类型,很长,全部作为索引大大增加存储空间,索引也需要维护,对于长字符串,又想作为索引列,可取的办法就是取前一部分(局部),代表一整列作为索引串。
如何确保这个前缀能代表或大致代表这一列?MySQL中有个概念是 索引选择性
,是指索引中不重复的值的数目(也称基数X)与整个表该列记录总数(T)的比值。基数可以通过SHOW INDEX FROM 表名
查看。
比如一个列表 [1,2,2,3,5,6],总数是 6,不重复值数目为 5,选择性为 5/6,因此选择性范围是[X/T, 1],这个值越大,表示列中不重复值越多,越适合作为局部索引,而唯一索引(UNIQUE KEY)的选择性是1。
EXPLAIN SELECT * FROM staff where remark LIKE ‘xxx%’;
可以看到key_len的值非常大,此时对 remark 字段重建局部索引:
ALTER TABLE staff DROP INDEX idx_remark_part, ADD INDEX idx_remark_part(remark(5));
再次查询:key_len变成了18。
4. 索引优化总结
上面罗列出了一些使用索引过程中的优化,但是索引不宜滥用创建。下面总结出不宜创建索引的情况以及一些创建索引的建议:
-
更新非常频繁字段不宜建索引:
因为字段更新台频繁,会导致B+树的频繁的变更,重建索引。所以这个过程是十分消耗数据库性能的。
-
区分度不大的字段不宜建索引:
比如类似性别这类的字段,区分度不大,建立索引的意义不大。因为不能有效过滤数据,性能和全表扫描相当。另外注意一点,返回数据的比例在
30%
之外的,优化器不会选择使用索引。 -
业务中有唯一特性的字段,建议建成
唯一索引
业务中如果有唯一特性的字段,即使是多个字段的组合,也尽量都建成唯一索引。尽管唯一索引会影响插入效率,但是对于查询的速度提升是非常明显的。此外,还能够提供校验机制,如果没有唯一索引,高并发场景下,可能还会产生脏数据。
-
多表关联时,要确保关联字段上必须有索引
-
最佳索引实践口诀
全值匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
Like百分写最右,覆盖索引不写星;
不等空值还有or,索引失效要少用;
VAR引号不可丢,SQL高级也不难!
-
EXPLAIN
执行计划实践总结如果还是觉得
EXPLAIN
执行计划列太多了,也记不住呀,那么请重点关注以下几列:第1列
:ID越大,执行的优先级越高;ID相等,从上往下优先顺序执行。第2列
:select_type 查询语句的类型,SIMPLE简单查询,PRIMARY复杂查询,DERIVED衍生查询(from子查询的临时表),派生表。第4列
:请重点掌握,type类型,查询效率优先级:system->const->eq_ref->ref->range->index->ALLALL
是最差
的,system
是最好
的,性能最佳,阿里巴巴开发规约中要求最差也得到range
级别,而不能有index、ALL
。
文章大部分开源于公众号:Java爱好者社区 ,作者东升的思考。 但是原文章有些内容测试结果不太一样不知道是不是版本问题,本文使用的是myql5.7。
引,高并发场景下,可能还会产生脏数据。
-
多表关联时,要确保关联字段上必须有索引
-
最佳索引实践口诀
全值匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
Like百分写最右,覆盖索引不写星;
不等空值还有or,索引失效要少用;
VAR引号不可丢,SQL高级也不难!
-
EXPLAIN
执行计划实践总结如果还是觉得
EXPLAIN
执行计划列太多了,也记不住呀,那么请重点关注以下几列:第1列
:ID越大,执行的优先级越高;ID相等,从上往下优先顺序执行。第2列
:select_type 查询语句的类型,SIMPLE简单查询,PRIMARY复杂查询,DERIVED衍生查询(from子查询的临时表),派生表。第4列
:请重点掌握,type类型,查询效率优先级:system->const->eq_ref->ref->range->index->ALLALL
是最差
的,system
是最好
的,性能最佳,阿里巴巴开发规约中要求最差也得到range
级别,而不能有index、ALL
。
文章大部分开源于公众号:Java爱好者社区 ,作者东升的思考。 但是原文章有些内容测试结果不太一样不知道是不是版本问题,本文使用的是myql5.7。