MySQL进阶1 - 索引
- 1. 索引概述
- 2. 索引结构
- 2.1 二叉树
- 2.2 B-Tree(多路平衡查找树)
- 2.3 B+Tree
- 2.4 Hash
- 3. 索引分类
- 4. 索引语法
- 5. SQL性能分析
- 5.1 SQL执行频率
- 5.2 慢查询日志
- 5.3 profile
- 5.4 explain执行计划
- 5.3.1 EXPLAIN执行计划各字段含义:
- 6. 索引使用
- 6.1 最左前缀法则
- 6.2 范围查询
- 6.3 索引列运算
- 6.4 字符串不加引号
- 6.5 模糊匹配
- 6.6 or连接的条件
- 6.7 数据分布影响
- 6.8 SQL提示
- 6.9 覆盖索引
- 6.10 前缀索引
- 6.11 单列索引与联合索引
- 7. 索引设计原则
1. 索引概述
介绍
索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。
优缺点
优势 | 劣势 |
---|---|
提高数据检索的效率,降低数据库的IO成本 | 索引列也是要占用空间的 |
通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗 | 索引大大提高了查询效率,同时也降低更新表的速度,如对表进行INSERT、UPDATE、DELETE时,效率降低 |
2. 索引结构
MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的结构,主要包含以下几种:
索引结构 | 描述 |
---|---|
B + Tree结构 | 最常见的索引类型,大部分引擎都支持B+Tree索引 |
Hash索引 | 底层数据结构是用哈希表实现的,只有精确匹配索引列的查询才有效,不支持范围查询 |
R+tree(空间索引) | 空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少 |
Full - text(全文索引) | 是一种通过建立倒排索引,快速匹配文档的方式,类似于Lucene,Solr,ES |
索引 | InnoDB | MyISAM | Memory |
---|---|---|---|
B + Tree索引 | 支持 | 支持 | 支持 |
Hash索引 | 不支持 | 不支持 | 支持 |
R - tree索引 | 不支持 | 支持 | 不支持 |
Full - text | 5.6版本之后支持 | 支持 | 不支持 |
我们平常说的索引,如果没有特别指明,都是指B+tree结构组织的索引
2.1 二叉树
二叉树缺点:顺序插入时,会形成一个链表,查询性能大大降低。数据量大的情况下,层级较深,检索速度慢
红黑树:数据量大的情况下,层级较深,检索速度慢
2.2 B-Tree(多路平衡查找树)
以一棵最大度数(max-degree)为5(5阶)的B-Tree为例(每个节点最多存储4个key,5个指针):
Ps:树的度数指的是一个节点的子节点的个数
上图中有4个key,分别是20、30、62、89,其对应5个指针,小于20的对应第一个指针,20-30之间对应第二个指针,30-62之间对应第三个指针,62-89之间对应第四个指针,大于89的对应第五个指针
具体可参考B-Tree Visualization,在该网站中选择Max. Degree = 5,插入以下数据:
100 65 169 368 900 556 780 35 215 1200 234 888 158 90 1000 88 120 268 250
效果如下:
● 5阶的B树,每一个节点最多存储4个key,对应5个指针
● 一旦节点存储的key数量达到5,就会裂变,中间元素向上分裂
● 在B树种,非叶子节点和叶子节点都会存放数据
2.3 B+Tree
以一棵最大度数(max-degree)为4(4阶)的B+Tree为例:
在B+ Tree Visualization中选择5阶,同样插入以上数据,效果如下:
相对于B-Tree区别:
① 所有的数据都会出现在叶子节点
② 叶子节点形成一个单向链表
MySQL索引数据结构对经典的B+Tree进行了优化。在原B+Tree的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能
2.4 Hash
哈希索引就是采用一定的Hash算法,将键值换算成新的Hash值,映射到对应的槽位上,然后存储在Hash表中。如果两个(或多个)键值,映射到一个相同的槽位上,他们就产生了Hash冲突(也称为Hash碰撞),可以通过链表来解决问题
• Hash索引特点
- Hash索引只能用于对等比较(=, in),不支持范围查询(between, >, <, …)
- 无法利用索引完成排序操作
- 查询效率高,通常只需要一次检索就行了,效率通常要高于B+Tree索引
• 存储引擎支持
在MySQL中,支持Hash索引的是Memory引擎,而InnoDB中具有自适应Hash功能,Hash索引是存储引擎根据B+Tree索引在指定条件下自动构建的
3. 索引分类
分类 | 含义 | 特点 | 关键字 |
---|---|---|---|
主键索引 | 针对于表中主键创建的索引 | 默认自动创建,只能有一个 | PRIMARY |
唯一索引 | 避免同一个表中某数据列中的值重复 | 可以有多个 | UNIQUE |
常规索引 | 快速定位特定数据 | 可以有多个 | |
全文索引 | 全文索引查找得是文本中得关键词,而不是比较索引中的值 | 可以有多个 | FULLTEXT |
在InnoDB存储引擎中,根据索引的存储形式,又可以分为以下两种:
分类 | 含义 | 特点 |
---|---|---|
聚集索引(Clustered Index) | 将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据 | 必须有,且只有一个 |
二级索引(Secondary Index) | 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 | 可以存在多个 |
聚集索引选取规则:
• 如果存在主键,主键索引就是聚集索引
• 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引
• 如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引
4. 索引语法
• 创建索引
CREATE [UNIQUE | FULLTEXT] INDEX index_name ON table_name (index_col_name, …);
• 查看索引
SHOW INDEX FROM table_name;
• 删除索引
DROP INDEX index_name ON table_name;
• 案例演示
-- 创建表tb_user
create table tb_user(
id int auto_increment primary key comment '主键ID',
name varchar(10) comment '姓名',
phone char(11) comment '手机号',
email varchar(30) comment '邮箱',
profession varchar(20) comment '专业',
age int check ( age > 0 and age < 100 ) comment '年龄',
gender int check ( gender = 1 or gender = 2 ) comment '性别',
status char(1) comment '状态',
createtime datetime comment '创建时间'
) comment '信息表';
-- 添加数据
insert into tb_user(name, phone, email, profession, age, gender, status, createtime)
values('安景辉', 13080354567, 'anjinghui@example.com', '软件工程', 23, 1, 6, '2001-02-02 00:00:00'),
('白佳颖', 13097742368, 'baijiaying@example.com', '通讯工程', 26, 2, 0, '2001-03-05 00:00:00'),
('柏凡', 13123456789, 'bofan@example.com', '英语', 25, 1, 2, '2002-03-02 00:00:00'),
('陈梦洁', 13245678901, 'chenmengjie@example.com', '工程造价', 27, 2, 0, '2001-07-02 00:00:00'),
('曹宇涛', 13356789012, 'caoyutao@example.com', '软件工程', 22, 1, 1, '2001-04-22 00:00:00'),
('董碧云', 13467890123, 'dongbiyun@example.com', '舞蹈', 22, 2, 0, '2001-02-07 00:00:00'),
('单景瑞', 13578901234, 'shanjingrui@example.com', '应用数学', 27, 1, 0, '2001-02-08 00:00:00'),
('冯彩云', 13689012345, 'fengcaiyun@example.com', '化工', 28, 2, 5, '2001-05-23 00:00:00'),
('范石磊', 13790123456, 'fanshilei@example.com', '金属材料', 30, 1, 0, '2001-09-18-00:00:00'),
('关海燕', 13801234567, 'guanhaiyan@example.com', '机械工程及其自动化', 27, 2, 2, '2001-08-16 00:00:00'),
('高少杰', 13912345678, 'gaoshaojie@example.com', '无机非金属材料工程', 26, 1, 0, '2001-06-12 00:00:00'),
('韩楚仪', 13112345678, 'hanchuyi@example.com', '会计', 23, 2, 0, '2001-05-11 00:00:00'),
('郝飞羽', 13212345678, 'haofeiyu@example.com', '工程造价', 32, 1, 1, '2001-04-09 00:00:00'),
('贺冰倩', 13312345678, 'hebingqian@example.com', '应用数学', 25, 2, 2, '2001-04-10 00:00:00'),
('黄浩然', 13412345678, 'huanghaoran@example.com', '软件工程', 24, 1, 3, '2001-02-12 00:00:00'),
('黄欣茹', 13512345678, 'huangxinru@example.com', '软件工程', 24, 2, 0, '2001-01-30 00:00:00'),
('江逸舟', 13612345678, 'jiangyizhou@example.com', '工业经济', 35, 1, 0, '2000-05-03 00:00:00'),
('李佳妮', 13712345678, 'lijiani@example.com', '化工', 38, 2, 1, '2001-08-08 00:00:00'),
('康正乐', 13812345678, 'kangzhengle@example.com', '国际贸易', 30, 1, 0, '2007-03-12 00:00:00'),
('刘可颐', 13987654321, 'liukeyi@example.com', '城市规划', 39, 2, 0, '2001-08-15 00:00:00'),
('李海洋', 13012345678, 'lihaiyang@example.com', '城市规划', 40, 1, 0, '2000-04-12 00:00:00'),
('马一鸣', 13187654321, 'mayiming@example.com', '土木工程', 21, 1, 3, '2002-07-18 00:00:00'),
('林泽洋', 13287654321, 'linzeyang@example.com', '城市园林', 20, 1, 0, '2002-03-10 00:00:00'),
('秦芷妍', 13387654321, 'qinzhiyan@example.com', '工程造价', 29, 2, 4, '2003-05-26 00:00:00');
-- 查询索引
show index from tb_user;
-- 1.name字段为姓名字段,该字段的值可能会重复,为该字段创建索引
create index idx_user_name on tb_user(name);
-- 2. phone手机号字段的值是非空且唯一的,为该字段创建唯一索引
create unique index idx_user_phone on tb_user(phone);
-- 3. 为profession、age、status创建联合索引
create index idx_user_pro_age_sta on tb_user(profession, age, status);
-- 4. 为email建立合适的索引来提升查询效率
create index idx_user_email on tb_user(email);
-- 删除索引
drop index idx_user_name on tb_user;
drop index idx_user_pro_age_sta on tb_user;
drop index idx_user_phone on tb_user;
drop index idx_user_email on tb_user;
查询结果如下:
5. SQL性能分析
5.1 SQL执行频率
MySQL客户端连接成功后,通过show[session | global] status命令可以提供服务器状态信息。通过如下指令,可以查看当前数据库的 INSERT、UPDATE、DELETE、SELECT 的访问频次:
SHOW GLOBAL STATUS LIKE ‘Com_______’;
其中,show session status是查看当前会话的状态信息,show global status是查看全局的状态信息,如下:
5.2 慢查询日志
慢查询日志记录了所有执行时间超过指定参数(long _query_time,单位:秒,默认10秒) 的所有SQL语句的日志。MySQL的慢查询日志默认没有开启,查询慢查询日志开关是否开启的指令如下:
SHOW VARIABLES LIKE ‘slow_query_log’;
而查询慢查询日志时间的指令如下:
SHOW VARIABLES LIKE ‘long_query_time’;
Linux(centOS)中需在MySQL的配置文件(etc/my.cnf)中配置如下信息以开启慢查询日志开关:
# 开启MySQL慢查询日志查询开关
slow_query_log=1
# 设置慢查询日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2
详细配置及操作如下:
① 首先输入以下指令对/etc/my.cnf进行编辑
vi /etc/my.cnf
② 按’i’键进行编辑,并在文档末尾添加如下语句:
# 开启慢查询日志开关
slow_query_log=1
# 设置慢查询日志时间为2秒
long_query_time=2
③ 按’Esc’键退出编辑,输入’:x’或’:wq’或’:wq!'保存并退出
④ 重启MySQL服务器
systemctl restart mysqld
⑤ 慢查询日志文件存路径:/var/lib/mysql/localhost-slow.log
如该位置没有该文件可在MySQL中通过命令show varaiables like ‘%slow_query_log_file%’查看慢查询日志存放位置
⑥ 查看localhost-slow.log内容
cat localhost-slow.log
从中可以看到,除了MySQL版本号、端口号等信息外并无其他多余信息
⑦ 查看慢查询日志文件尾部实时输出的内容的命令(单独新建个窗口以便查看实时输出)
tail -f localhost-slow.log
⑧ 在另一窗口执行MySQL指令,如查询tb_user表的数据:
select * from tb_user;
可以看到,由于查询时间未超过2秒,所以慢查询日志实时输出并没有出现数据。接下来使数据库睡眠3秒,再查看慢日志查询实时输出:
select sleep(3);
此时,睡眠的指令可以在慢查询日志中看到,而Windows端的慢查询日志开启请参考https://www.cnblogs.com/taozihua/p/10331897.html
5.3 profile
show profiles能够在做SQL优化时帮助我们了解时间都花费在哪里。通过have_profiling参数,能够看到当前MySQL是否支持profile操作:
SELECT @@have_profiling;
profiling默认是关闭的,可以通过以下指令查看profiling是否开启:
SELECT @@profiling;
若为0,则为关闭状态,若为1,则为开启状态
若profiling处于关闭状态,可通过set语句开启profiling:
set [global | session] profiling=1
开启profiling后,执行一系列的业务SQL操作,然后通过以下指令查看指令的执行耗时:
# 查看每一条SQL的耗时基本情况
show profiles;
# 查看指定query_id的SQL语句各个阶段的耗时情况
show profile for query query_id;
# 查看指定query_id的SQL语句CPU的使用情况
show profile cpu for query query_id;
案例演示
-- 查询profiling是否打开
select @@profiling;
-- 开启profiling
set profiling = 1;
-- 执行SQL操作
select * from tb_user;
select * from tb_user where id = 15;
select * from tb_user where name = '单景瑞';
select count(*) from tb_user;
select * from tb_user where gender = 1;
select sleep(3);
-- 查看每一条sql的耗时基本情况
show profiles;
此时,运行结果如下:
-- 查看指定query_id的SQL语句各个阶段的耗时情况
show profile for query 11;
此时,运行结果如下:
-- 查看指定query_id的SQL语句CPU的使用情况
show profile cpu for query 11;
5.4 explain执行计划
EXPLAIN或DESC命令获取MySQL如何执行SELECT语句的信息,包括SELECT语句执行过程中表如何连接和连接的顺序
语法:
# 直接在select语句之前加上关键字explain / desc
EXPLAIN SELECT 字段列表 FROM 表名 WHERE 条件;
演示
explain select * from tb_user where id = 8;
结果如下:
5.3.1 EXPLAIN执行计划各字段含义:
id
select 查询的序列号,表示查询中执行select子句或是操作表的顺序(id相同,执行顺序从上到下;id不同,值越大,越先执行)
select_type
表示SELECT的类型,常见的取值有 SIMPLE (简单表,即不使用表连接或子查询)、PRIMARY (主查询,即外层的查询)、UNION (UNION 中的第二个或者后面的查询语句),SUBQUERY ( SELECT / WHERE 之后包含了子查询)等
type
表示连接类型,性能由好到差的连接类型为 NULL、system、const、eq_ref、ref、range、index、all
possible_key
显示可能应用在这张表上的索引,一个或多个
key
实际使用的索引,如果为NULL,则没有使用索引
key_len
表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好
rows
MySQL认为必须要执行查询的行数,在innodb引擎的表中,是一个估计值,可能并不总是精确的
filtered
表示返回结果的行数占需读取行数的百分比,filtered的值越大越好
案例演示
案例中所演示的表分别为students、courses、student_course以及tb_user,在MySQL基础4-多表查询以及本文中可以找到对应的表
id
explain select s.* from students s where s.id in (select sc.student_id from student_course sc where sc.sourse_id = (select c.id from courses c where c.name = 'Python'));
运行结果如下:
从图中可以看到,SQL语句编写顺序是students、student_course、courses,但从执行结果来看,id最大的为3,
对应为courses表,这是最先执行的,其次是id为2的student_course表,然后id相同的则由上而下顺序执行,也就
是先执行<subquery 2>(第二个子查询),最后才执行students表的查询。
select_type
select_type参考意义不大,它只是说明当前SQL语句的查询类型
type
type代表访问的类型,如:
explain select * from tb_user where id = 1;
在该查询中,连接类型为const,而type访问类型中,性能由好至差分别为 NULL、system、const、eq_ref、ref、range、
index、all。其中 system 只有访问系统表的时候才会出现,根据主键、唯一索引进行访问,一般会出现const,如:
show index from tb_user;
select * from tb_user;
explain select * from tb_user where phone = '13689012345';
如果使用非唯一性的索引进行查询时,就会出现ref,如:
explain * from tb_user where name = '黄浩然';
在优化SQL语句的时候,尽量把type往前优化,尽量不要出现all,如果出现all的话,代表全表扫描,性能会比较低
如果出现index,代表用了索引,但也会对索引进行扫描,虽然比all快,但性能也比较低
extra代表额外的信息,也就是说,在执行查询的过程当中,在前面几个字段中没有展现出来的值,将会在extra进行展示
关于posssible_key、key、key_len、rows 在此不多做演示
重点关注: type、possible_key、key、ken_len
6. 索引使用
6.1 最左前缀法则
如果索引了多列(联合索引),要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。如果跳跃某一列,索引将部分失效(后面的字段索引失效)。
案例演示
本案例中所用的表为本文中创建的tb_user表
explain select * from tb_user where profession = '软件工程' and age = 24 and status = '0';
运行结果如下:
该查询用到了联合索引,索引长度为93,profession、age、status这三个字段的索引全用上了,符合最左前缀法则,且中间并未跳过任何一列
explain select * from tb_user where profession = '软件工程' and age = 24;
运行结果如下:
该查询索引长度为88,意味着status字段的索引长度为5,该查询也满足最左前缀法则,中间并未跳过某一列
explain select * from tb_user where profession = '软件工程';
运行结果如下:
该查询语句只查询了profession字段,并未跳过某一列,所以也满足最左前缀法则,且可由前两条查询结果推断出age 的索引长度为5
select * from tb_user where profession = '软件工程' and status = '0';
运行结果如下:
该查询索引长度为83,意味着profession用到索引,而status没用到,所以索引长度和单独查询profession字段的索引长度是一样的。原因是profession和status间还有一个字段age被跳过,这导致了索引部分失效,也就是status字段的索引失效
explain select * from tb_user where age = 24 and status ='0';
运行结果如下:
该查询用到的是全表扫描而不是索引,因为最左边的profession字段并未存在,所以该查询不满足最左前缀法则,导致索引失效
explain select * from tb_user where age = 24 and status = '0' and profession = '软件工程';
运行结果如下:
该查询也用到了索引,因为最左前缀法则要求的是最左列必须存在,但并不意味着最左列的顺序必须是最左边,所以该查询语句满足最左前缀法则
总结: 最左前缀法则要求包含索引中最左边的列,如果最左边的列不存在,索引失效,如果跳过了其中某一列,该列后面的索引将会失效
6.2 范围查询
联合索引中,出现范围查询(>, <),范围查询右侧的列索引失效
案例演示
explain select * from tb_user where profession = '软件工程' and age > 23 and status = '0';
运行结果如下:
结合前文来看,该查询满足最左前缀法则,但索引长度为88,说明age后面的那一列,也就是status索引失效。而在业务允许的情况下,尽量用 >= 或 <= 来代替,以规避索引失效的情况
explain select * from tb_user where profession = '软件工程' and age >= 23 and status = '0';
运行结果如下:
6.3 索引列运算
不要在索引列上进行运算操作,否则索引将失效
案例演示
-- 查询手机尾号为56的用户信息
select * from tb_user where substring(phone,10,2) = '56';
-- 查看执行信息
explain select * from tb_user where substring(phone,10,2) = '56';
运行结果如下:
可以看到,在索引列phone上引用函数运算substring后,索引失效,所以在业务中应尽量减少对索引列进行运算的操作
6.4 字符串不加引号
字符串类型字段使用时,不加引号,索引将失效
案例演示
select * from tb_user where phone = 13912345678;
explain select * from tb_user where phone = 13912345678;
运行结果如下:
在该查询语句中,由于phone字段作为字符串在查询的时候未加引号,导致索引失效
6.5 模糊匹配
如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引将会失效
案例演示
-- 尾部模糊匹配
select * from tb_user where profession like '软件%';
explain select * from tb_user where profession like '软件%';
-- 头部模糊匹配
select * from tb_user where profession like '%工程';
explain select * from tb_user where profession like '%工程';
运行结果如下:
从中可以看到,在进行尾部模糊匹配时索引并未失效,但对头部进行模糊匹配时索引失效了
6.6 or连接的条件
用or分割开的条件,如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到
案例演示
explain eslect * from tb_user where id = 15 or age = 24;
explain select * from tb_user whre phone = '13412345678' or age = 24;
运行结果如下:
由于age字段没有索引,只有联合索引,而如果仅仅只对age字段进行查询,不满足最左前缀法则,联合索引不会用到。而正由于联合索引用不到,所以即使id、phone有索引,索引也会失效,所以需要针对age字段也建立索引
-- 建立age字段索引
create index idx_user_age on tb_user(age);
-- 再次查询
explain select * from tb_user where id = 15 or age = 24;
explain select * from tb_user where phone = '13412345678' or age = 24;
运行结果如下:
此时可以看到,在建立age字段的索引后,用到的是phone字段及age字段的索引,而非联合索引,所以该查询索引未失效
6.7 数据分布影响
如果MySQL评估使用索引比全表扫描更慢,则不使用索引
** 案例演示**
explain select * from tb_user where phone >= '13612345678';
explain select * from tb_user where phone >= '13112345678';
explain select * from tb_user where phone >= '13012345678';
运行结果如下:
从中可以看到,在查询phone >= '13612345678’时用到了索引,这是因为MySQL在查询时评估该表中大部分phone值都小于该值,用索引查询比全表扫描快。但将phone改为’13112345678’或‘13012345678’时,MySQL评估全表大部分phone值都比这俩值大,用全表扫描会比用索引快,所以才进行全表扫描,同理还有:
explain select * from tb_user where profession is null;
explain select * from tb_user where profession is not null;
在该查询中,由于tb_user表中的profession字段没有null值,所以第一个查询语句用到的是索引,而第二个查询语句用到的则是全表扫描,因为MySQL评估当查询时需要比较大部分值时,会优先使用全表扫描,而如果只是一小部分的话,则优先使用索引。当将profession字段的值都设置为null后,结果将相反,这里不做演示
6.8 SQL提示
SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些认为的提示来达到优化操作的目的
在查询时,如果一个字段有多个索引,MySQL查询优化器会自动选择索引,如下:
-- 创建tb_user表中profession字段的索引
create index idx_user_pro on tb_user(profession);
-- 查询
explain select * from tb_user where profession = '软件工程';
运行结果如下:
可以看到,在该查询中可能用到的索引分别为’idx_user_pro_age_sta’及’idx_user_pro’,而实际用到的索引为’idx_user_pro_age_sta’,这就是MySQL查询优化器自动选择的结果,如果想指定查询索引,可参考以下三条查询语句:
use index(告诉数据库用哪个索引):
explain select * from tb_user use index(idx_user_pro) where profession = ‘软件工程’;
该查询语句只是给MySQL建议用指定查询,至于查询结果,取决于MySQL比较所指定的索引效率快不快后所给出的
ignore index(告诉数据库不用哪个索引):
explain select * from tb_user ignore index(idx_user_pro) where profession = ‘软件工程’;
force index(告诉数据库必须走哪个索引):
explain select * from tb_user force index(idx_user_pro) where profession = ‘软件工程’;
6.9 覆盖索引
尽量使用覆盖索引(查询用了索引,并且需要返回的列,在该索引中已经全部能够找到),减少select *
案例演示
-- 删除多余索引,以减少干扰
drop index idx_user_age on tb_user;
drop index idx_user_email on tb_user;
drop index idx_user_pro on tb_user;
-- 测试
explain select id, profession from tb_user where profession = '软件工程' and age = 24 and status = '0';
explain select id, profession, age from tb_user where profession = '软件工程' and age = 24 and status = '0';
explain select id, profession, age, status from tb_user where profession = '软件工程' and age = 24 and status = '0';
explain select id, profession, age, status, name from tb_user where profession = '软件工程' and age = 24 and status = '0';
explain select * from tb_user where profession = '软件工程' and age = 24 and status = '0';
运行结果如下:
从中可以看到Extra中的信息出现不同的地方,而其中’Using where; Using index’的语句的性能要高于’Using index contition’,还需注意的是,如果使用的MySQL版本不同Extra中出现的值,其展示的形式也是不同的
Ps:
using index condition:查找使用了索引,但是需要回表查询数据
using where; using index:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据
6.10 前缀索引
当字段类型为字符串(varchar,text等)时,有时候需要索引很长的字符串,这回让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率。此时可以只将字符串的一部分前缀建立索引,这样可以大大节约索引空间,从而提高索引效率
创建前缀索引的语法
create index idx_xxx_xxx on table_name(column(n));
前缀长度
可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的
选择性计算演示
select count(distinct email) / count(*) from tb_user;
select count(substring(email, 1, 5)) / count(*) from tb_user;
运行结果如下:
创建前缀索引演示
create index idx_email_5 on tb_user(email(5));
show index from tb_user;
运行结果如下:
6.11 单列索引与联合索引
单列索引:即一个索引只包含单个列
联合索引:即一个索引包含了多个列
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引
单列索引情况
explain select id, phone, name from tb_user where phone = '13012345678' and name = '李海洋';
多条件联合查询时,MySQL优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询
联合索引情况
-- 创建联合索引
create unique index idx_user_phone_name on tb_user(phone, name);
explain select id, phone, name from tb_user where phone = '13012345678' and name = '李海洋';
-- 强制使用联合索引
explain select id, phone, name from tb_user force index(idx_user_phone_name) where phone = '13012345678' and name = '李海洋';
运行结果如下:
7. 索引设计原则
Ⅰ. 针对数据量较大,且查询比较频繁的表建立索引
Ⅱ. 针对常作为查询条件(where)、排序(ordder by)、分组(group by)操作的字段建立索引
Ⅲ. 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高
Ⅳ. 如果是字符串类型的字段,字段的长度较长,可以针对字段的特点,建立前缀索引
Ⅴ. 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率
Ⅵ. 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率
Ⅶ. 如果索引不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询