SQL优化主要就是在优化索引。
索引的弊端:
1.索引本身很大, 可以存放在内存/硬盘(通常为硬盘)
2.索引不是所有情况均适用: a.少量数据 b.频繁更新的字段 c.很少使用的字段
3.索引会降低增删改的效率(增删改查)
优势:1.提高查询效率(降低IO使用率)
2.降低CPU使用率 (...order by age desc,因为 B树索引 本身就是一个 好排序的结构,因此在排序时 可以直接使用)
索引分类:
主键索引 :不能重复。id 不能是null
唯一索引 :不能重复。id 可以是null
单值索引 :单列, age ;一个表可以多个单值索引,name。
复合索引 :多个列构成的索引 (相当于 二级目录 ) (name,age) (a,b,c,d,...,n)
创建索引:
方式一:create 索引类型 索引名 on 表(字段)
单值索引:create index dept_index on tb(dept);
唯一索引:create unique index name_index on tb(name) ;
复合索引:create index dept_name_index on tb(dept,name);
方式二:alter table 表名 索引类型 索引名(字段)
单值索引:alter table tb add index dept_index(dept) ;
唯一索引:alter table tb add unique index name_index(name);
复合索引:alter table tb add index dept_name_index(dept,name);
注意:如果一个字段是primary key,则改字段默认就是主键索引
删除索引:
drop index 索引名 on 表名 ;
drop index name_index on tb ;
查询索引:
show index from 表名 ;
show index from 表名 \G;
SQL性能分析:
查询执行计划: explain +SQL语句
explain select * from tb ;可以模拟SQL优化器执行SQL语句,从而让开发人员知道自己编写的SQL状况
优化方法,官网:https://dev.mysql.com/doc/refman/5.5/en/optimization.html
id : 标识符
select_type :查询类型
table :输出行表 partitions:匹配的分区
type :联接类型
possible_keys :可能的索引选择
key :实际使用的索引
key_len :所选键的长度
ref :与索引比较的列
rows :估计要检查的行
filtered:按表条件过滤的行百分比
Extra :额外的信息
部分列详细解释
- id: id值相同,从上往下 顺序执行,数据小的表 优先查询; id值不同:id值越大越优先查询;
- select_type:
PRIMARY:包含子查询SQL中的 主查询 (最外层)
SUBQUERY:包含子查询SQL中的 子查询 (非最外层)
simple:简单查询(不包含子查询、union)
derived:衍生查询(使用到了临时表)
a.在from子查询中只有一张表
explain select cr.cname from ( select * from course where tid in (1,2) ) cr b.在from子查询中, 如果有table1 union table2 ,则table1 就是derived,table2就是union
explain select cr.cname from ( select * from course where tid = 1 union select * from course where tid = 2 ) cr ;
union:上例
union result :从 union 临时表检索结果的 select
3.type:索引类型
依次从最优到最差分别为:system>const>eq_ref>ref>range>index>all ,要对type进行优化的前提:有索引 。 其中:system,const只是理想情况;实际能达到 ref>range ; system(忽略): 只有一条数据的系统表 ;或衍生表只有一条数据的主查询。
const:仅仅能查到一条数据的SQL ,用于Primary key 或unique索引 (类型与索引类型有关)
explain select tid from test01 where tid =1 ;
alter table test01 drop primary key ;
create index test01_index on test01(tid) ;
eq_ref:唯一性索引:对于每个索引键的查询,返回匹配唯一行数据(有且只有1个,不能多 、不能0)
select ... from ..where name = ... .常见于唯一索引和主键索引。
alter table teacherCard add constraint pk_tcid primary key(tcid);
alter table teacher add constraint uk_tcid unique index(tcid) ;
explain select t.tcid from teacher t,teacherCard tc where t.tcid = tc.tcid ;
以上SQL,用到的索引是 t.tcid,即teacher表中的tcid字段;
如果teacher表的数据个数和连接查询的数据个数一致(都是3条数据),则有可能满足eq_ref级别;否则无法满足。
ref:非唯一性索引,对于每个索引键的查询,返回匹配的所有行(0行或者多行)
准备数据:
insert into teacher values(4,'tz',4) ;
insert into teacherCard values(4,'tz222');
测试:
alter table teacher add index index_name (tname) ;
explain select * from teacher where tname = 'tz';
range:检索指定范围的行 ,where后面是一个范围查询(between ,> < >=, 特殊:in有时候会失效 ,从而转为无索引all)
alter table teacher add index tid_index (tid) ;
explain select t.* from teacher t where t.tid in (1,2) ;
explain select t.* from teacher t where t.tid <3 ;
index:查询全部索引中数据
explain select tid from teacher ; --tid 是索引, 只需要扫描索引表,不需要所有表中的所有数据
all:查询全部表中的数据
explain select cid from course ; --cid不是索引,需要全表扫描,即需要所有表中的所有数据
system/const: 结果只有一条数据
eq_ref:结果多条;但是每条数据是唯一的 ;
ref:结果多条;但是每条数据是是0或多条 ;
4.rows: 被索引优化查询的数据个数 (实际通过索引而查询到的数据个数)
explain select * from course c,teacher t where c.tid = t.tid
and t.tname = 'tz' ;
5.key_len:这一列显示了mysql在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些列
key_len计算规则如下:
字符串
char(n):n字节长度
varchar(n):2字节存储字符串长度,如果是utf-8,则长度 3n + 2
数值类型
tinyint:1字节
smallint:2字节
int:4字节
bigint:8字节
时间类型
date:3字节
timestamp:4字节
datetime:8字节
如果字段允许为 NULL,需要1字节记录是否为 NULL
索引最大长度是768字节,当字符串过长时,mysql会做一个类似左前缀索引的处理,将前半部分的字符提取出来做索引。
6. Extra:
(i).using filesort : 性能消耗大;需要“额外”的一次排序(查询) 。常见于 order by 语句中。
explain select * from test02 where a1 ='' order by a2 ; --using filesort
小结:对於单索引, 如果排序和查找是同一个字段,则不会出现using filesort;如果排序和查找不是同一个字段,则会出现 using filesort;
避免: where哪些字段,就order by哪些字段
复合索引:不能跨列(最佳左前缀)
drop index idx_a1 on test02;
drop index idx_a2 on test02;
drop index idx_a3 on test02;
alter table test02 add index idx_a1_a2_a3 (a1,a2,a3) ;
explain select *from test02 where a1='' order by a3 ; --using filesort
explain select *from test02 where a2='' order by a3 ; --using filesort
explain select *from test02 where a1='' order by a2 ;
explain select *from test02 where a2='' order by a1 ; --using filesort
小结:避免: where和order by 按照复合索引的顺序使用,不要跨列或无序使用。
(ii). using temporary:性能损耗大 ,用到了临时表。一般出现在group by 语句中。
explain select a1 from test02 where a1 in ('1','2','3') group by a1 ;
explain select a1 from test02 where a1 in ('1','2','3') group by a2 ; --using temporary
避免:查询那些列,就根据那些列 group by .
(iii). using index :性能提升; 索引覆盖(覆盖索引)。原因:不读取原文件,只从索引文件中获取数据 (不需要回表查询)
只要使用到的列 全部都在索引中,就是索引覆盖using index
例如:test02表中有一个复合索引(a1,a2,a3)
explain select a1,a2 from test02 where a1='' or a2= '' ; --using index
drop index idx_a1_a2_a3 on test02;
alter table test02 add index idx_a1_a2(a1,a2) ;
explain select a1,a3 from test02 where a1='' or a3= '' ;
如果用到了索引覆盖(using index时),会对 possible_keys和key造成影响:
a.如果没有where,则索引只出现在key中;
b.如果有where,则索引 出现在key和possible_keys中。
explain select a1,a2 from test02 where a1='' or a2= '' ;
explain select a1,a2 from test02 ;
(iii).using where (需要回表查询)
假设age是索引列
但查询语句select age,name from ...where age =...,此语句中必须回原表查Name,因此会显示using where.
explain select a1,a3 from test02 where a3 = '' ; --a3需要回原表查询
(iv). impossible where :where子句永远为false
explain select * from test02 where a1='x' and a1='y' ;
索引数据结构:
- 二叉树
- 红黑树
- Hash表
- B-Tree
根据这个链接可以模仿这些如何进行增加节点,转换的:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
为什么索引会采用B+Tree数据结果呢?以下分析:
二叉树,在某种情况下回会退化成链表结构:
红黑树会进行自适应,进行平衡,但是数据量大的时候也会出现问题,深度过高,会增加I/O次数:
hash表:hash查询速度也快,但是进行范围查找就不行。
B-Tree:横向扩展,高度低。(横向查找在同一页中,不需要更多的磁盘I/O,内存中的遍历速度可以忽略不计)
B+Tree:有指针,能很好的支撑范围查找。(叶子节点中其实是双向指针)
索引最佳实践
使用的表
CREATE TABLE `employees` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
`age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',
`position` varchar(20) NOT NULL DEFAULT '' COMMENT '职位',
`hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',
PRIMARY KEY (`id`),
KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='员工记录表';
INSERT INTO employees(name,age,position,hire_time) VALUES('LiLei',22,'manager',NOW());
INSERT INTO employees(name,age,position,hire_time) VALUES('HanMeimei', 23,'dev',NOW());
INSERT INTO employees(name,age,position,hire_time) VALUES('Lucy',23,'dev',NOW());
最佳实践
1. 全值匹配
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei';
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22;
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manager';
2.最佳左前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
EXPLAIN SELECT * FROM employees WHERE age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE position = 'manager';
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';
3.不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';
EXPLAIN SELECT * FROM employees WHERE left(name,3) = 'LiLei';
4.存储引擎不能使用索引中范围条件右边的列
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age > 22 AND position ='manager';
5.尽量使用覆盖索引(只访问索引的查询(索引列包含查询列)),减少select *语句
EXPLAIN SELECT name,age FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';
6.mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描
EXPLAIN SELECT * FROM employees WHERE name != 'LiLei'
7.is null,is not null 也无法使用索引
EXPLAIN SELECT * FROM employees WHERE name is null
8.like以通配符开头('$abc...')mysql索引失效会变成全表扫描操作
EXPLAIN SELECT * FROM employees WHERE name like '%Lei'
EXPLAIN SELECT * FROM employees WHERE name like 'Lei%'
问题:解决like'%字符串%'索引不被使用的方法?
a)使用覆盖索引,查询字段必须是建立覆盖索引字段
EXPLAIN SELECT name,age,position FROM employees WHERE name like '%Lei%';
b)当覆盖索引指向的字段是varchar(380)及380以上的字段时,覆盖索引会失效!
9.字符串不加单引号索引失效
EXPLAIN SELECT * FROM employees WHERE name = '1000';
EXPLAIN SELECT * FROM employees WHERE name = 1000;
10.少用or,用它连接时很多情况下索引会失效
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' or name = 'HanMeimei';
like KK%相当于=常量,%KK和%KK% 相当于范围