14. 索引

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
RDS MySQL DuckDB 分析主实例,集群系列 8核16GB
简介: 14. 索引

什么是索引?

类似字典或者电话簿里的目录,可以加速查询。 多数情况下,索引很小,可以放进内存里。

但是索引也有代价,比如增加数据库的大小,因为他们必须永久存储在表旁边;每次添加、修改和删除时,MySQL必须更新对应的索引,这会影响正常操作的性能

所以我们应该为性能关键的表创建索引,因为索引的最终目的是加速查询,如果基于表的设计为所有表添加索引,是浪费资源的,不要盲目创建索引。

索引在内存中通常表示为二叉树。

创建索引

sql

复制代码

explain select customer_id from customers where state = 'CA';

执行上面语句后,会返回如下信息:

id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE customers ALL 1012 10.00 Using where
  • type 表示当前的查询是 ALL,即会在查询的表中遍历所有的记录,全表扫描
  • rows 表示遍历查询总共有 1012 行 显然如果用户数量增加,遍历的查询会越来越慢。

sql

复制代码

create index idx_state on customers (state);

通过上面的命令可以创建一个索引,此时再去解释查询语句,就会变成下面:

|id|select_type|table|partitions|type|possible_keys|key|key_len|ref|rows|filtered|Extra| |---|---|---|---|---|---|---|---|---|---|---|---|---| |1|SIMPLE|customers||ref|idx_state|idx_state|8|const|112|100.00|Using where; Using index|

  • type 是 ref,没有再做全表扫描
  • rows 中扫描的记录条数明显减少
  • possible_keys 表示 MySQL执行查询时可能会考虑的索引,可能会有多个索引
  • key MySQL实际使用的索引

练习

sql

复制代码

explain select customer_id from customers where points > 1000;
create index idx_points on customers (points);

id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE customers range idx_points idx_points 4 529 100.00 Using where; Using index

查看索引

sql

复制代码

show indexes in customers;
Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Visible Expression
customers 0 PRIMARY 1 customer_id A 12 BTREE YES
customers 1 idx_state 1 state A 48 BTREE YES
customers 1 idx_points 1 points A 789 BTREE YES
  • Key_name 表示索引名称 第一个 PRIMARY 表示聚集索引,每个表自动有的主键默认是聚集索引,每个表也只有一个聚集索引第二个和第三个都是二级索引还有一种是外键,也是二级索引,可以快速链接到别的表上
  • Column_name 表示索引在的列上
  • Collation 表示数据在索引中的排序规则,A表示升序,D表示降序
  • Cardinality 表示索引中唯一值的估计数量,不是实际数量,要想获得实际数量,可以先执行analyze table customers
  • Index_type 表示索引类型 BTREE 表示二分树

前缀索引

当索引的列是字符串时(包括 CHAR、VARCHAR、TEXT、BLOG),尤其是当字符串较长时,我们通常不会使用整个字符串而是只是用字符串的前面几个字符来建立索引,这被称作 Prefix Indexes 前缀索引,这样可以减少索引的大小使其更容易在内存中操作

sql

复制代码

create index idx_lastname on customers (last_name(20))

上面是创建前缀索引的方法,但是如何选取前缀的值呢?

  • 如果值太少,达不到彻底加速查询的目的,相同前缀的结果还是会遍历查找
  • 如果值太多,会造成空间和时间的浪费 所以必须找到一个恰当的值设置为前缀索引长度

sql

复制代码

select 
  count(distinct left(last_name,1)), -- 25
  count(distinct left(last_name,5)), -- 967
    count(distinct left(last_name,10)) -- 997
from customers;

执行的目的是用找到一个最小的数,通过最少的前缀字符,能够匹配出尽可能多的原文中的记录 显然在这里 5 最优,因为 1 得到的太少,而 10 增长的数据量有限。

全文索引

sql

复制代码

select *
from posts
where title like '%react redux%' or
  body like '%react redux%';

像这种搜索关键词的查询使用like并不方便,一是没有索引会导致全表扫描,效率低下;二是返回结果只会包含关键词,不符合搜索引擎的效果。

通过建立 全文索引 来实现搜索引擎的搜索 建立全文索引:

sql

复制代码

CREATE FULLTEXT INDEX idx_title_body ON posts (title, body);

利用全文索引,结合 MATCH 和 AGAINST 进行 google 式的模糊搜索:

sql

复制代码

select *,match(title,body) against('react redux')
from posts
where match(title,body) against('react redux');

注意:MATCH后的括号里必须包含全文索引 idx_title_body 建立时相关的所有列,不然会报错

全文检索有两个模式:

  • 自然语言模式,自然语言模式是默认模式,也是上面用到的模式。
  • Boolean模式,可以更明确地选择包含或排除一些词汇(google也有类似功能)

sql

复制代码

select *,match(title,body) against('react redux')
from posts
where match(title,body) against('react -redux +form' in boolean mode);
  • in boolean mode 表示使用Boolean模式
  • - 表示排除后面的关键词,+表示必须出现后面的关键词,使用双引号 "" 表示必须准确包含这个短语

总结

需要全文搜索的字段,比如描述,文章内容等 不需要全文搜索的字段,比如姓名,地址,使用前缀索引就够了

复合索引

sql

复制代码

show indexes in customers;
explain select customer_id from customers
where state = 'CA' and points > 1000;
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE customers ref idx_state,idx_points idx_state 8 const 112 52.27 Using index condition; Using where

通过上面的语句的执行结果可以发现,MySQL 只使用了一个state上的索引,所以

  • MySQL 不管一条查询条件会有多少个索引,只会选择一个索引使用
  • 对于索引之外的列,依然是使用全部遍历的方法来查找,但后半部分的数据量非常大的时候,索引的效率会降低。

复合索引可以同时对多列创建索引

sql

复制代码

create index idx_state_points on customers (state,points);

再次执行上面解释查询语句,可以明显发现查询的数量减少了(rows 从112 到 58),效率提高了

id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE customers range idx_state,idx_points,idx_state_points idx_state_points 12 58 100.00 Using where; Using index

在 possible_keys 上可以发现有三个索引,当时 MySQL 发现复合索引效率最高,所以自动选择了他。

注意:MySQL 会给每个索引自动加上主键。 所以过多的索引会占用大量储存空间,而且数据每次数据更新都会重建索引,所以过多的索引也会拖慢更新速度实际中更多的是用到组合索引,一个组合索引最多可组合 16 列,根据实际的查询需求和数据量来考虑复合索引的列数

删除索引

sql

复制代码

drop index idx_state on customers;
drop index idx_points on customers;

复合索引中列的顺序

有两条基本规则:

  1. 需要对列进行排序,让更频繁使用的列在前面。这有助于缩小查询范围
  2. 把基数更高的列排在前面,基数表示索引中唯一值的数量,可以把表分割成在尽可能小,最大限度地缩小查询数量 这两条规则不是固定的,必要时还是具体问题具体分析。

sql

复制代码

-- 先给last_name索引,再给state索引
create index idx_lastname_state on customers
(last_name, state);
-- 先给state索引,再给last_name索引
create index idx_state_lastname on customers
(state,last_name);
explain select customer_id from customers
where state = 'CA' and last_name like 'A%';
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE customers range idx_lastname,idx_state_points,idx_lastname_state,idx_state_lastname idx_state_lastname 210 7 100.00 Using where; Using index

MySQL 决定使用第二个索引,它要扫描的数量最少。

强制规定使用那个索引

sql

复制代码

explain select customer_id from customers
use index (idx_lastname_state)
where state = 'CA' and last_name like 'A%';

高效索引的建议

对于查询语句,虽然where last_name like 'A%' and state = 'CA'where state = 'CA' and last_name like 'A%'的执行结果类似。like的范围模糊,=的约束性更高

但是后者的第一步查询使用索引可以将范围限制的更小,约束性更高,所以执行的效率更高

sql

复制代码

explain select customer_id from customers
use index (idx_lastname_state)
where state like 'A%' and last_name like 'A%'; -- 41行
explain select customer_id from customers
use index (idx_state_lastname)
where state like 'A%' and last_name like 'A%'; -- 51行

通过上面的例子可以发现,对于一个查询,应该是约束性越强的放在前面,当 state 从约束性高的=变为约束性较低的like后,执行idx_state_lastname索引的效率不如idx_lastname_state

所以,高效索引结论是:对于一条有多个不同列条件的查询,约束性高的条件所在列,可以在复合索引中的排序靠前,即优先执行

无用索引

sql

复制代码

explain select customer_id from customers
use index (idx_state_lastname)
where last_name like 'A%';

注意,对于复合查询出现的列,如果查询条件没有这一个列,相当于这里有一部分无用索引。 如果有这样的查询条件需求,最好在这一列上单独再创建一个索引。

复合索引提高效率的总结

  1. 让更频繁使用的列在前面
  2. 基数更高的列排在前面
  3. 考虑查询条件,具体分析

当索引失效时

例子1

sql

复制代码

explain select customer_id from customers
where state = 'CA' or points > 1000;
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE customers index idx_state_points,idx_state_lastname idx_state_points 12 1012 34.72 Using where; Using index

上面的结果中可以发现,他比全表扫描快,因为它不需要从磁盘里读取数据,还是只需要在内存中查询。虽然查询的类型是使用索引,但是有接近全表扫描的查询

这种时候,必须重写查询,以尽可能最好的方式利用索引。

sql

复制代码

create index idx_points on customers (points); --
explain 
  select customer_id from customers
  where state = 'CA' 
    union -- 自动去重记录
    select customer_id from customers
  where points > 1000;
  • 原本使用的idx_state_points索引可以快速定位 state ,所以可以不需要再单独添加一列索引
  • 添加一个idx_points 再快速找到 points > 1000 的记录
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 PRIMARY customers ref idx_state_points,idx_lastname_state,idx_state_lastname idx_state_points 8 const 112 100.00 Using where; Using index
2 UNION customers range idx_state_points,idx_points idx_points 4 529 100.00 Using where; Using index
3 UNION RESULT <union1,2> ALL Using temporary

从结果可以发现,先前扫描了 1000+ 条记录,但是现在只需要扫描 600+ 条记录,有了不小的提升。

例子2

sql

复制代码

explain select customer_id from customers
where points + 10 > 2010;
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE customers index idx_points 4 1012 100.00 Using where; Using index

因为有了一个表达式,MySQL必须对这张表全表扫描,索引完全失效。

可以直接简化这个表达式。

使用索引排序

sql

复制代码

explain select customer_id from customers 
order by state;
explain select customer_id from customers 
order by first_name;
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE customers index idx_state_points 12 1012 100.00 Using index

第一次:通过 state 列排序,MySQL会自动选择有关于这一列的索引,比如 idx_state_points 。Using index 表示使用到了索引进行排序。

1 SIMPLE customers ALL 1012 100.00 Using filesort

第二次:对 first_name 排序,ALL 就意味着进行了全表扫描,Using filesort 意味着使用了“外部排序( MySQL 的一种排序算法,很耗费资源的一种操作)”

通过show status like 'last_query_cost' 查看 MySQL 服务器的环境变量,发现第一次的花费是 1000+ ,第二次只有 100+

要使用索引排序的基本规则

sql

复制代码

explain select customer_id from customers 
order by state,points ;
  1. 不能出现索引中没有出现的列索引肯定没有办法使用的order by state,points,first_name
  2. 索引一般都是升序的,使用降序(desc)也会导致全表扫描,因为排序方向不一致。但是可以对单独的列进行降序排序。order by state,points desc 不行order by state desc,points desc 可以
  3. 排序列的顺序和索引中出现的一致,前缀也不能少。但是索引前缀列条件满足后可以继续使用索引排序order by points,stateorder by points都不行,但是有一个例外,可以先通过索引进入到一个 state 里面,然后再通过索引进行排序,例如where state = 'CA' order by state,points可以

覆盖索引

sql

复制代码

explain select customer_id from customers -- 使用上了索引,100+
order by state;
explain select * from customers -- 全表扫描,索引不够,1000+
order by state;
show status like 'last_query_cost';

使用索引三步

  1. 先查看 where 这种查找语句是否有索引条件
  2. 再查看 order by 排序语句是否有索引条件
  3. 最后查看 select 后选出的列是否有索引条件

如果 SELECT 子句里选择的列在索引中,整个查询就可以在不碰原表的情况下完成,这叫作覆盖索引(covering index),即索引满足了查询的所有需求所以全程不需要使用原表,这是最快的

维护索引

重复索引

例如:(A,B,C)(A,B,C) MySQL不会阻止重复索引创建,所以这回造成资源浪费。

多余索引

例如:(A,B)(A) 因为原来的索引也可以优化包含 A 列的查询

总结

维护索引要去掉重复索引、多余索引和未被使用的索引。 总之,创建索引之前,一定要先查看一下已经有的索引。


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
缓存 API 开发者
魔搭社区牵手FastChat&vLLM,打造极致LLM模型部署体验
FastChat是一个开放平台,用于训练、服务和评估基于LLM的ChatBot。
|
敏捷开发 测试技术 持续交付
云效产品使用常见问题之直接git clone 输入账密可以拉代码,但是云效平台上不行如何解决
云效作为一款全面覆盖研发全生命周期管理的云端效能平台,致力于帮助企业实现高效协同、敏捷研发和持续交付。本合集收集整理了用户在使用云效过程中遇到的常见问题,问题涉及项目创建与管理、需求规划与迭代、代码托管与版本控制、自动化测试、持续集成与发布等方面。
|
机器学习/深度学习 BI
机器学习模型评估指标总结
机器学习模型评估指标总结
768 2
|
人工智能
Sora信息问题之模拟对象状态变化存在的局限如何解决
Sora信息问题之模拟对象状态变化存在的局限如何解决
118 0
|
算法
【算法挨揍日记】day06——1004. 最大连续1的个数 III、1658. 将 x 减到 0 的最小操作数
1004. 最大连续1的个数 III 题目描述: 给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。
506 1
|
开发工具
STM32第六章-定时器详解
定时器(Timer)最基本的功能就是定时了,比如定时发送 USART 数据、定时采集 AD数据等等。如果把定时器与 GPIO 结合起来使用的话可以实现非常丰富的功能,可以测量输入信号的脉冲宽度,可以生产输出波形。定时器生产 PWM 控制电机状态是工业控制普遍方法,这方面知识非常有必要深入了解。
600 0
STM32第六章-定时器详解
|
流计算
老板要我开发一个简单的工作流,15 次需求变更,我干到秃了。。
一天,老板找到我,说要做个简单的工作流引擎。 我查了一天啥是工作流,然后做出了如下版本:
265 0
老板要我开发一个简单的工作流,15 次需求变更,我干到秃了。。
|
IDE 开发工具 C++
《Visual C++数字图像模式识别技术详解(第2版)》一2.2 Visual C++数字图像处理
本节书摘来自华章出版社《Visual C++数字图像模式识别技术详解(第2版)》一 书中的第2章,第2.2节,作者:冯伟兴 贺波 王臣业,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
2568 0