MySQL 数据结构优化与索引细节解析:打造高效数据库的优化秘笈(二)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云解析 DNS,旗舰版 1个月
简介: MySQL 数据结构优化与索引细节解析:打造高效数据库的优化秘笈(二)

索引匹配方式

关于 explain 关键字各个列描述可以阅读:
MySQL 内置的监控工具介绍及使用篇

首先创建好表结构,并设置好对应的索引

CREATE TABLE `member` (
  `id` bigint(10) not null primary key auto_increment,
  `nick_name` varchar(32) not null default '' comment '昵称',
  `real_name` varchar(32) not null default '' comment '真实姓名',
  `phone` varchar(11) not null default '' comment '手机号',
  `age` int not null default 0 comment '年龄',
  `level_id` bigint(10) not null default 1 comment '等级id',
  `level_name` varchar(32) not null default 1 comment '等级名称',
   `register_time` datetime not null default current_timestamp comment '注册时间'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
alter table `member` add index idx_name_phone_level(nick_name,phone, level_id);

新增一部分测试数据

insert into `member`
(nick_name,real_name,phone,age,level_id,level_name) values
('January','一月','1111111101',18,1,'青铜'),
('February','二月','1111111102',18,2,'白银'),
('March','三月','1111111103',18,3,'黄金'),
('April','四月','1111111104',18,3,'黄金'),
('May','五月','1111111105',18,3,'黄金'),
('June','六月','1111111106',18,3,'黄金'),
('July','七月','1111111107',18,4,'铂金'),
('August','八月','1111111108',18,5,'钻石'),
('September','九月','1111111109',18,5,'钻石'),
('October','十月','1111111110',18,5,'钻石'),
('November','十一月','1111111111',18,6,'翡翠'),
('December','十二月','1111111112',18,7,'大师');

全值匹配

全值匹配:索引内的值都是等值查询,例如:

mysql> explain select * from `member` where phone='1111111101' and level_id=1 and nick_name='January';
+----+-------------+--------+------------+------+----------------------+----------------------+---------+-------------------+------+----------+-------+
| id | select_type | table  | partitions | type | possible_keys        | key                  | key_len | ref               | rows | filtered | Extra |
+----+-------------+--------+------------+------+----------------------+----------------------+---------+-------------------+------+----------+-------+
|  1 | SIMPLE      | member | NULL       | ref  | idx_phone_level_name | idx_name_phone_level | 184     | const,const,const |    1 |   100.00 | NULL  |
+----+-------------+--------+------------+------+----------------------+----------------------+---------+-------------------+------+----------+-------+

最左前缀匹配

最左前缀匹配:匹配组合索引列的部分列,例如:

mysql> explain select * from `member` where phone='1111111101'  and nick_name='January';
+----+-------------+--------+------------+------+----------------------+----------------------+---------+-------------+------+----------+-------+
| id | select_type | table  | partitions | type | possible_keys        | key                  | key_len | ref         | rows | filtered | Extra |
+----+-------------+--------+------------+------+----------------------+----------------------+---------+-------------+------+----------+-------+
|  1 | SIMPLE      | member | NULL       | ref  | idx_name_phone_level | idx_name_phone_level | 176     | const,const |    1 |   100.00 | NULL  |
+----+-------------+--------+------------+------+----------------------+----------------------+---------+-------------+------+----------+-------+

如上,只会匹配到 nick_name、phone 字段

列前缀匹配

列前缀匹配:匹配某一列值的开头部分,例如:

mysql> explain select * from `member` where nick_name like 'Ja%';
+----+-------------+--------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+
| id | select_type | table  | partitions | type  | possible_keys        | key                  | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+--------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | member | NULL       | range | idx_name_phone_level | idx_name_phone_level | 130     | NULL |    1 |   100.00 | Using index condition |
+----+-------------+--------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+

虽然是模糊匹配方法,但也用到了索引,但以下这种方式是使用不到索引的

mysql> explain select * from `member` where nick_name like '%nuary';
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | member | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   12 |    11.11 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+

范围匹配

范围匹配:查询某一个范围的数据,例如:

mysql> explain select * from `member` where nick_name > 'May';
+----+-------------+--------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+
| id | select_type | table  | partitions | type  | possible_keys        | key                  | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+--------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | member | NULL       | range | idx_name_phone_level | idx_name_phone_level | 130     | NULL |    3 |   100.00 | Using index condition |
+----+-------------+--------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+

精确匹配某一列、范围匹配另一列

精确匹配某一列、范围匹配另一列:第一列的值全值匹配,另外的列部分匹配,例如:

mysql> explain select * from `member` where nick_name = 'May' and phone > '1111111101';
+----+-------------+--------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+
| id | select_type | table  | partitions | type  | possible_keys        | key                  | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+--------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | member | NULL       | range | idx_name_phone_level | idx_name_phone_level | 176     | NULL |    1 |   100.00 | Using index condition |
+----+-------------+--------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+

访问索引

访问索引:查询时只需要访问索引,从二级索引树可以拿到所有需要的数据,无需通过主键再次去回表查询,本质上就是覆盖索引,例如:

mysql> explain select id,phone,level_id,nick_name from `member` where phone='1111111101' and level_id=1 and nick_name='January';
+----+-------------+--------+------------+------+----------------------+----------------------+---------+-------------------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys        | key                  | key_len | ref               | rows | filtered | Extra       |
+----+-------------+--------+------------+------+----------------------+----------------------+---------+-------------------+------+----------+-------------+
|  1 | SIMPLE      | member | NULL       | ref  | idx_name_phone_level | idx_name_phone_level | 184     | const,const,const |    1 |   100.00 | Using index |
+----+-------------+--------+------------+------+----------------------+----------------------+---------+-------------------+------+----------+-------------+

Extra 列显示 Using index,则表达直接可以在索引取回

哈希索引

在 InnoDB、MyISAM 存储引擎中使用 B+ 树存储索引,MySQL Memory 存储引擎中显示支持哈希索引,它基于 Hash 表实现,只有精确匹配所有列的查询才有效,哈希索引自身只需存储对应的 hash 值,所以索引结构十分紧凑,才让哈希索引查找速度非常快

哈希索引使用的限制如下:

  1. 只包含了哈希值、行指针,而不存储字段值,索引不能使用索引中的值来避免读取行,每次查询必须要先匹配到哈希值,然后再读取行指针,再根据行指针去读取我们实际的数据
  2. 数据不是按照索引值顺序存储的,所以无法排序
  3. 不支持部分列查询,哈希索引是使用索引的全部内容来计算哈希值
  4. 访问哈希索引数据非常快,除非有很多的哈希冲突,当出现哈希冲突时,存储引擎必须遍历链表中所有的行指针,逐行进行比较,直到找出所有符合条件的行
  5. 哈希冲突比较多时,维护的代价也会很高

当需要大量的 URL,并且通过 URL 进行搜索查找,若使用 B+ 树,存储的内容就会变的很大

select id,url from url_base where url = '';

此时,可以利用 CRC32 对 url 作哈希算法,使用以下的查询方式:

select id,url from url_base where url_crc = CRC32('');

此查询性能较高的原因 > 使用体积很小的索引来完成查找

组合索引

通常在实际生产开发情况下,会选择多列值作为索引,也就是组合索引,又称之为复合索引;在使用组合索引时,要注意最左匹配原则,当创建组合索引之后,进行列值匹配时,从左到右匹配

创建一张表,将表中 a,b,c 三列作为组合索引,注意不同查询语句的索引匹配情况,如下:

语句 索引是否匹配
where a=3 是,使用了 a
where a=3 and b=5 是,使用了 a,b
where a=3 and b=5 and c=4 是,使用了 a,b,c
where b=5、where c=4
where a=3 and c=4 是,使用了 a
where a=3 and b > 10 and c=7 是,使用了 a,b
where a=3 and b like ‘%xx%’ and c=7 是,使用了 a

聚簇、非聚簇索引

所谓的聚簇索引并不是单独的索引类型,而是一种数据存储方式聚簇索引指的是数据、索引列紧凑的存储在一起;非聚簇索引指的是数据、索引分开存储

聚簇索引优点:

  1. 可以把相关数据保存在一起
  2. 数据访问更快,数据、索引保存在同一颗树中
  3. 使用覆盖索引扫描的查询可以直接使用叶子节点的主键值

聚簇索引缺点:

  1. 聚簇数据最大限度提高了 IO 密集型应用的性能,若数据全部放在内存,那么聚簇索引就没有优势
  2. 插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式
  3. 更新聚簇索引列代价很高,会强制将每个被更新的行移动到新的位置(在数据表已经堆积了很多数据再加索引导致的,最好是先暂停表数据的使用,在停用的状态下添加索引
  4. 基于聚簇索引的表在插入新行或者主键被更新导致需要移动行的时候,可能面临页分裂问题
  5. 聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏或者由于页分裂导致数据存储不连续的时候

覆盖索引

一个索引包含所有需要查询的字段值,称之为覆盖索引

并非所有索引的类型都可以称之为覆盖索引,覆盖索引必须要存储索引列的值

不同的存储引擎实现覆盖索引的方式不同,不是所有的存储引擎都支持覆盖索引,Memory 不支持覆盖索引

覆盖索引优点:

  1. 索引条目通常远小于数据行大小,若只需要读取索引,那么 MySQL 就会极大减少数据的访问量
  2. 索引是按照列值顺序存储的,所以对于 IO 密集型的范围查询会比从一行一行读取数据的 IO 少得多
  3. MyISAM 存储引擎在内存中只缓存索引,数据依赖于操作系统来缓存,因此访问数据需要进行一次系统调用,可能会导致严重的性能问题
  4. 由于 InnoDB 是聚簇索引,所以覆盖索引对 InnoDB 特别有用

当发起一个被索引覆盖的查询时,在 explain > extra 列可以看到 using index 信息,此时就使用了覆盖索引

mysql> explain select store_id,film_id from inventory;
+----+-------------+-----------+------------+-------+---------------+----------------------+---------+------+------+----------+-------------+
| id | select_type | table     | partitions | type  | possible_keys | key                  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-----------+------------+-------+---------------+----------------------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | inventory | NULL       | index | NULL          | idx_store_id_film_id | 3       | NULL | 4581 |   100.00 | Using index |
+----+-------------+-----------+------------+-------+---------------+----------------------+---------+------+------+----------+-------------+

在大多数存储引擎中,覆盖索引只能覆盖那些只访问索引中部分列的查询;不过,可以进一步的进行优化,可以使用 InnoDB 二级索引来覆盖查询

例如:actor 表使用 InnoDB 存储引擎,并在 last_name 字段有索引,虽然该索引的列不包含主键 actor_id,但仍然能够对 actor_id 作覆盖查询,二级索引叶子节点也以主键作为唯一 Key 存储了起来

mysql> explain select actor_id,last_name from actor where last_name='HOPPER';
+----+-------------+-------+------------+------+---------------------+---------------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys       | key                 | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------------+---------------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | actor | NULL       | ref  | idx_actor_last_name | idx_actor_last_name | 182     | const |    2 |   100.00 | Using index |
+----+-------------+-------+------------+------+---------------------+---------------------+---------+-------+------+----------+-------------+

优化细节(important)

数据库勿做计算

数据库勿计算:当使用索引列进行查询时,尽量不要使用表达式,把计算逻辑放到代码业务层而不是在数据库层操作

mysql> explain select * from actor where actor_id=4;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | actor | NULL       | const | PRIMARY       | PRIMARY | 2       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from actor where actor_id+1=4;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | actor | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  200 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+

如上,当 where 条件中包含了表达式以后,不会使用对应的索引列

尽量主键查询

尽量使用主键查询,而不是使用其他索引,因此主键查询不会触发回表查询 > explain 分析出来会是 const,效率极高

前缀索引

前缀索引:当创建的索引字符串比较长时,可以考虑使用索引前缀 > 创建索引,来提高数据检索的效率

1、有时需要索引很长的字符串,这会让索引变得很大且很慢,通常情况下,可以使用某个列开始的部分字符串,这样可以大大节约索引空间,从而提高索引效率,但这会大大降低索引的选择性索引选择性越高则查询效率越高,因为选择性更高的索引可以让 MySQL 在查找时过滤掉更多的行

2、一般情况下某个列前缀的选择性也是足够高的,足以满足查询的性能,但是对于 Blob、Text、Varchar 类型列,必须使用前缀索引,因为 MySQL 不允许索引这些列的完整长度,使用该方法的诀窍在于要选择足够长的前缀以此来保证较高的选择性

举例如下,先创建好对应的表结构、数据:

# 创建数据表
create table citydemo(city varchar(50) not null);
insert into citydemo(city) select city from city;
# 重复执行 5 次,以下的 SQL 语句
insert into citydemo(city) select city from citydemo;
# 随机更新城市表中的名称
update citydemo set city=(select city from city order by rand() limit 1);

数据准备好以后,进行 SQL 测试,如下:

  1. 查询最常见的城市列表,发现每个值出现了 40 次以上
mysql> select count(*) cnt,city from citydemo group by city order by cnt desc limit 10;
+-----+-------------+
| cnt | city        |
+-----+-------------+
|  73 | London      |
|  49 | Chatsworth  |
|  48 | Ikerre      |
|  47 | Tychy       |
|  46 | Santa Rosa  |
|  45 | Alessandria |
|  45 | Akron       |
|  44 | Tartu       |
|  44 | Kuwana      |
|  44 | Tsuyama     |
+-----+-------------+

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
5天前
|
存储 自然语言处理 关系型数据库
MySQL高级篇——索引的创建与设计原则
索引的分类与使用、MySQL8.0索引新特性、适合创建索引的情况、不适合创建索引的情况
MySQL高级篇——索引的创建与设计原则
|
5天前
|
存储 SQL 关系型数据库
MySQL高级篇——索引失效的11种情况
索引优化思路、要尽量满足全值匹配、最佳左前缀法则、主键插入顺序尽量自增、计算、函数导致索引失效、类型转换(手动或自动)导致索引失效、范围条件右边的列索引失效、不等于符号导致索引失效、is not null、not like无法使用索引、左模糊查询导致索引失效、“OR”前后存在非索引列,导致索引失效、不同字符集导致索引失败,建议utf8mb4
MySQL高级篇——索引失效的11种情况
|
14天前
|
存储 关系型数据库 MySQL
MySQL基础:索引
MySQL中的索引是一种数据结构,能大幅提升数据库查询效率和减少I/O成本,类似于书的目录帮助快速定位内容。其优势包括提高检索效率和降低排序成本,但会占用空间并影响更新表的效率。鉴于查询远多于更新,索引仍被推荐使用。索引分为多种类型,如B+树和哈希索引,其中B+树因其较低的高度和稳定的查询开销成为常用选择。创建和删除索引需谨慎,以免影响性能。
40 4
MySQL基础:索引
|
5天前
|
存储 SQL 关系型数据库
【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)
MySQL调优主要分为三个步骤:监控报警、排查慢SQL、MySQL调优。 排查慢SQL:开启慢查询日志 、找出最慢的几条SQL、分析查询计划 。 MySQL调优: 基础优化:缓存优化、硬件优化、参数优化、定期清理垃圾、使用合适的存储引擎、读写分离、分库分表; 表设计优化:数据类型优化、冷热数据分表等。 索引优化:考虑索引失效的11个场景、遵循索引设计原则、连接查询优化、排序优化、深分页查询优化、覆盖索引、索引下推、用普通索引等。 SQL优化。
【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)
|
5天前
|
存储 缓存 关系型数据库
MySQL高级篇——存储引擎和索引
MyISAM:不支持外键和事务,表锁不适合高并发,只缓存索引,内存要求低,查询快MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM不支持事务、行级锁、外键,有一个毫无疑问的缺陷就是崩溃后无法安全恢复。5.5之前默认的存储引擎优势是访问的速度快,对事务完整性没有要求或者以SELECT、INSERT为主的应用针对数据统计有额外的常数存储。故而 count(*) 的查询效率很高表名.frm 存储表结构;表名.MYD 存储数据 (MYData);
MySQL高级篇——存储引擎和索引
|
5天前
|
存储 关系型数据库 MySQL
MySQL高级篇——覆盖索引、前缀索引、索引下推、SQL优化、主键设计
覆盖索引、前缀索引、索引下推、SQL优化、EXISTS 和 IN 的区分、建议COUNT(*)或COUNT(1)、建议SELECT(字段)而不是SELECT(*)、LIMIT 1 对优化的影响、多使用COMMIT、主键设计、自增主键的缺点、淘宝订单号的主键设计、MySQL 8.0改造UUID为有序
MySQL高级篇——覆盖索引、前缀索引、索引下推、SQL优化、主键设计
|
3天前
|
存储 关系型数据库 MySQL
技术解析:MySQL中取最新一条重复数据的方法
以上提供的两种方法都可以有效地从MySQL数据库中提取每个类别最新的重复数据。选择哪种方法取决于具体的使用场景和MySQL版本。子查询加分组的方法兼容性更好,适用于所有版本的MySQL;而窗口函数方法代码更简洁,执行效率可能更高,但需要MySQL 8.0及以上版本。在实际应用中,应根据数据量大小、查询性能需求以及MySQL版本等因素综合考虑,选择最合适的实现方案。
18 6
|
7天前
|
SQL 关系型数据库 MySQL
MySQL技术安装配置、数据库与表的设计、数据操作解析
MySQL,作为最流行的关系型数据库管理系统之一,在WEB应用领域中占据着举足轻重的地位。本文将从MySQL的基本概念、安装配置、数据库与表的设计、数据操作解析,并通过具体的代码示例展示如何在实际项目中应用MySQL。
32 0
|
21天前
|
监控 网络协议 Java
Tomcat源码解析】整体架构组成及核心组件
Tomcat,原名Catalina,是一款优雅轻盈的Web服务器,自4.x版本起扩展了JSP、EL等功能,超越了单纯的Servlet容器范畴。Servlet是Sun公司为Java编程Web应用制定的规范,Tomcat作为Servlet容器,负责构建Request与Response对象,并执行业务逻辑。
Tomcat源码解析】整体架构组成及核心组件
|
1月前
|
存储 NoSQL Redis
redis 6源码解析之 object
redis 6源码解析之 object
53 6

推荐镜像

更多