MySQL索引原理以及SQL优化

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: MySQL索引原理以及SQL优化

一、索引


索引是一种有序的存储结构,按照单个或者多个列的值进行排序,以提升搜索效率。


1.1 索引分类

1.1.1 按数据结构分类

1)B+树索引


使用 B-Tree 数据结构来存储索引数据,适用于等值查询、范围查询和排序操作。B-Tree 的非叶子节点只存储索引信息,叶子节点存储具体数据信息;叶子节点之间互相连接,方便范

围查询。


2)hash索引


hash索引适用于等值查询,但不支持范围查询和排序操作。hash索引通过hash函数将关键字映射到具体的索引位置,因此查找速度非常快。但hash索引无法保证数据的顺序性,也不支持模糊查询。


3)全文索引


全文索引适用于对文本内容进行搜索的场景。它可以在文本列上进行模糊匹配LIKE,并支持关键字的自然语言搜索、词语扩展和排除等功能。但是工作中通常不会使用 mysql 提供的全文索引,而是使用一个开源的分布式搜索和分析引擎Elasticsearch 。


1.1.2 按物理存储分类

1)聚集索引(聚簇索引)

InnoDB的聚簇索引就是按照主键顺序构建 B+Tree结构。叶子节点存储数据行的数据。


2)辅助索引(二级索引)

InnoDB的辅助索引就是按照索引列构建 B+Tree结构。叶子节点存储的是索引列的值以及对应行的主键值


聚簇索引和二级索引的主要区别在于叶子节点是否存储数据。聚簇索引的叶子节点存储数据行的数据,因此找到了索引便找到了具体数据文件。由于数据和索引存储在一起,聚簇索引可以极大地提高主键查找的效率。


需要注意的是,由于聚簇索引决定了数据行的物理存储顺序,因此对聚簇索引的插入和更新操作可能会导致数据页的拆分和重新组织,进而引起额外的性能开销。


例:范围查找聚集索引 primary_key -> (18, 40)

a628c8fb2700df52d61e9011c08b716e_72470fdc63a04776b4f41f22a802359e.png


为什么要有二级索引?

聚簇索引的叶子节点存储了一行完整的数据,而二级索引只存储了主键值,相比于聚簇索引,占用的空间要少。当我们需要为表建立多个索引时,如果都是聚簇索引,那将占用大量内存空间,所以InnoDB中主键所建立的是聚簇索引,而唯一索引、普通索引、前缀索引等都是二级索引。

如果需要查询对应的数据行,需要回表查询,即在聚簇索引中进一步查找对应的数据行。这样可以避免在行移动或者插入新数据时出现的页分裂问题。


例:查找辅助索引 key = 33 的行记录

5f0586cfc5547fa76b684a19f9f661aa_28f393d6107846efb411c7d00c8f3f4c.png


1.1.3 按列属性分类

1)主键索引

非空唯一索引,一个表只有一个主键索引;在 innodb 中,主键索引的 B+ 树包含表数据信息

PRIMARY KEY(key) 

主键选择

innoDB 表是组织索引表,主键对应聚集索引 B+ 树,所有的数据都存储其中。每张表有且仅有一个主键。


  • 如果显示设置 PRIMARY KEY ,则该设置的key为该表的主键;
  • 如果没有显示设置,则从非空唯一索引中选择;

只有一个非空唯一索引,则选择该索引为主键;

有多个非空唯一索引,则选择声明的第一个为主键;

  • 没有非空唯一索引,则自动生成一个 6 字节的 _rowid 作为主键;

2)唯一索引

不可以出现相同的值,可以有NULL值。

UNIQUE(key) 

3)普通索引

允许出现相同的索引内容。


INDEX(key)
-- OR
KEY(key[,...])

4)前缀索引

在某些情况下,索引列的值可能较长,例如字符串类型的列。如果对整个值进行索引可能会占用大量的存储空间,降低查询性能。为了解决这个问题,可以使用前缀索引。前缀索引允许只对索引列的前几个字符进行索引,而不是对整个值进行索引。


-- 向名为"user"的表中添加名为"name"的列的前四个字符的索引
ALTER TABLE `student` ADD KEY(name(4));
-- 注意:前缀索引不能做 order by 和 group by

1.1.4 按列的个数索引

1)单列索引

对表上的单个列进行索引


2)组合索引

对表上的多个列进行索引

INDEX idx(key1,key2[,...]);
UNIQUE(key1,key2[,...]);
PRIMARY KEY(key1,key2[,...]);

1.2 索引的代价

1)存储空间:索引需要占用额外的存储空间。对于大型表和多个索引的情况,索引可能会占据相当大的存储空间。


2)降低DML效率:当对表进行DML(插入、更新或删除)操作时,索引也需要更新,因为需要动态维护B+树的索引有序性。


3)更新代价:对于具有多个索引的表,在更新操作时,可能需要同时更新多个索引。这可能会增加更新操作的时间和资源消耗。


4)索引维护成本:索引需要定期进行维护,以确保其保持有效和高效。这可能涉及到重新构建索引、重建统计信息等操作。


5)查询优化器的复杂性:有多个索引可供选择时,查询优化器必须选择最优的索引来执行查询。这可能会增加查询优化的复杂性和计算成本。


1.3 索引的使用场景

1)使用where条件判断时,会使用索引。


2)使用group by 和 order by,会判断后面的列是否创建了索引,如果创建了就会使用索引对应的B+树。


1.4 不使用索引的场景

1)没有涉及到where、grop by、order by的使用,不用创建索引。


2)区分度不高的列,没必要使用索引。


3)经常修改的列,不要创建索引;因为维护代价太高。


4)表的数据量少,没必要创建索引。


二、索引的实现原理


2.1 索引存储

innodb由段、区、页组成;段分为数据段、索引段、回滚段等。区大小为 1 MB(一个区由64个连续页构成);页的默认值为16k;页为逻辑页,磁盘物理页大小一般为 4K 或者 8K;为了保证区中的页的连续,存储引擎一般一次从磁盘中申请 4~5 个区。

f44d01a2a6d495ba80975a321cfb4637_a031be8ee14542168e8dabac24b7e411.png


2.2 页

记录是按行存储的,数据是按数据页读写的


页是 innodb 磁盘管理的最小单位;默认16k,对应 4 个物理磁盘页。可通过 innodb_page_size 参数来修改;B+ 树的一个节点的大小就是该页的值。


数据页之间通过双向链表的形式组织起来,逻辑上连续,物理上不连续。数据页内包含用户记录,每个记录之间用单向链表的方式组织起来,为了加快在数据页内高效查询记录,设计了一个页目录,页目录存储各个槽(分组),且主键值是有序的,于是可以通过二分查找法的方式进行检索从而提高效率。


2.3 InnoDB中的B+树

InnoDB 是 MySQL 数据库的默认存储引擎,它使用了 B+ 树作为索引结构来组织和存储数据。

B+树指的是多路平衡搜索树,用来组织磁盘数据,减少磁盘访问次数。B+树组织磁盘数据是以页为单位,物理磁盘页一般为4K,innodb 默认页大小为 16K;对页的访问是一次磁盘io,缓存中会缓存常访问的页。


每个索引对应着一个 B+ 树。B+树的一个节点对应一个数据页;B+树的层越高,那么要读取到内存的数据页越多,io次数越多。

bc456d5d19e1b315e9d9e21af5264650_bd263a95c25a4e408e1ea780f65d7c45.png


1)InnoDB中B+树的特征


多路平衡搜索树

非叶子结点只存储索引信息,叶子节点存储具体数据信息

所有的叶子节点都在同一层,且叶子节点间构成一个双向链表,方便范围查询。

节点的大小都是数据页的大小 (16KB) ,对应 4 个物理磁盘页 (4KB)


2)为什么 MySQL InnoDB 选择 B+ 树作为索引的数据结构?


 降低磁盘IO:B+树的非叶节点只包含键,而不包含真实数据,因此每个节点可以存储更多的记录个数。所以B+树的高度更低,访问时所需要的IO次数更少。此外,由于每个节点存储的记录数更多,所以对访问局部性原理的利用更好,缓存命中率更高。相比之下,红黑树在磁盘上的存储方式相对随机,导致磁盘 I/O 操作更频繁。哈希表则不适合直接存储在磁盘上,因为哈希表需要通过哈希函数计算位置,无法充分利用磁盘的顺序读写特性。


范围查询:B+树的叶子节点间构成一个双向链表,范围查询只需要对链表进行遍历即可。相比之下,红黑树和哈希表无法提供高效的范围查询支持。红黑树虽然能够支持有序访问,但在范围查询时需要遍历整个树,性能较差。而哈希表是基于哈希函数进行快速查找的,适用于单个键值查询,但对于范围查询则需要扫描整个表格,效率较低。


2.4 InnoDB的体系结构

ff408cba218972a19833357a439ecafb_62318bb879e641378514fabf8ff44a66.png

1)Buffer Pool(缓冲池)

Buffer Pool是一个用于存储数据和索引页的内存区域,它以固定大小的页为单位进行管理,通常以16KB为一页。它的作用是采用 LRU 算法将最常用的数据页(热点数据)保留在内存中,以减少对磁盘IO的需求。当数据被查询或更新时,首先通过自适应hash索引查询数据是否在buffer pool中;如果数据不在,则通过mmap将磁盘数据映射到buffer pool中;如果数据存在buffer pool中就直接操作。


2)Change Buffer(变更缓冲)

Change buffer 缓存非唯一索引的数据变更(DML操作),Change buffer 中的数据将会异步merge 到磁盘当中。当执行更新操作(如插入、更新、删除)时,InnoDB会将修改的数据页(包括数据和索引页)首先写入到Change Buffer中,而不是直接写入磁盘。Change Buffer是一个内存中的缓冲区,用于暂时存储待写入的修改操作。

4ce0f9482ab681fc4a2b2d7d807ddcb5_a51ae426583f455586b8aaa5bd5dee25.png


3)Change Buffer和Buffer Pool的区别

尽管Change Buffer和Buffer Pool都是内存中的缓存区域,但它们的功能和使用场景有所不同:

Change Buffer主要针对的是更新操作。它通过缓存待写入的修改操作,减少磁盘IO次数,提高写入性能。特别适用于批量插入或大规模更新操作,以及辅助索引的维护。

Buffer Pool主要针对的是读取操作。它通过将常用的数据页保留在内存中,减少磁盘IO需求,提高查询性能。特别适用于频繁的读取操作,可以根据工作负载和系统资源设置合适的大小。


4)innoDB 对于非聚集索引的 DML 操作流程

innoDB 对于非聚集索引的 DML 操作,不是每次直接插入到索引页,而是先判断插入的非聚集索引页(辅助索引页)是否在 buffer pool 中,若存在,则直接插入;若不在,则先放入 change buffer。然后通过定期对 change buffer 和 buffer pool 中辅助索引叶子节点的 merge 操作,将多个插入合并到一个操作(因为在同一非聚集索引页),这样就大大提升了对于非聚集索引插入的性能。


2.5 最左匹配原则

最左匹配原则是指在数据库查询中,当存在联合索引(Composite Index)时,索引会按照从左到右的顺序进行匹配,遇到 > < between like 就停止匹配。


最左匹配原则的好处在于可以充分利用联合索引的有序性,提高查询效率。例如,假设有一个联合索引 (A, B, C),查询条件为 WHERE A = 1 AND B = 2,那么索引可以直接定位到满足这两个条件的数据。但如果查询条件为 WHERE B = 2 AND C = 3,虽然索引中包含了 B 和 C 这两列,但由于查询条件无法匹配最左前缀 A,索引无法被使用。


需要注意的是,最左匹配原则仅适用于联合索引,对于单列索引则没有影响。另外,尽量将使用频率较高且区分度高的列放在联合索引的最左边,这样可以进一步提高查询效率。


2.6 覆盖索引

覆盖索引是一种数据查询方式,主要针对辅助索引。从辅助索引中就能找到数据,而不需通过聚集索引查找;利用辅助索引树高度一般低于聚集索引树, 可以较少的磁盘 IO。也就是,如果查询的字段是辅助索引,那么查询过程中就不需要回表查询,直接使用辅助索引B+树就可以查询到数据。


DROP TABLE IF EXISTS `covering_index_t`;
CREATE TABLE `covering_index_t` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255) DEFAULT NULL,
  `cid` INT(11) DEFAULT NULL,
  `age` SMALLINT DEFAULT 0,
  `score` SMALLINT DEFAULT 0,
  PRIMARY KEY (`id`),
  KEY `name_cid_idx` (`name`, `cid`)
)ENGINE = INNODB AUTO_INCREMENT=0 DEFAULT CHARSET = utf8;
INSERT INTO `covering_index_t` (`name`, `cid`, `age`, `score`)
VALUES
  ('mark', 10001, 12, 99),
  ('jack', 10002, 13, 98),
  ('rose', 10003, 14, 97),
  ('tom', 10004, 15, 100);
SHOW INDEX FROM `covering_index_t`;
-- 不会走索引
EXPLAIN SELECT * FROM `left_match_t` WHERE `cid` = 10001;
-- 需要回表查询
EXPLAIN SELECT * FROM `covering_index_t` WHERE `name` = 'tom';
-- 查询字段是辅助索引(`name`, `cid`, `id`),不需要回表查询
EXPLAIN SELECT `name`, `cid`, `id` FROM `covering_index_t` WHERE `name` = 'tom';

在使用中,尽量不要使用select * from …来获取数据;因为里面有些字段可能没有创建索引,没有创建索引就需要回表查询,这会增加磁盘IO。所以,在select中尽量写所需的字段。


三、索引失效


1)左模糊:LIKE 模糊查询,通配符 % 开头,则索引失效;例如: select * from user where name like’%Mark’;


2)索引字段参与运算:使用函数、进行表达式运算、隐式类型转换等操作,索引失效


3)在索引字段上使用 NOT <> != 索引失效;如果判断 id <> 0 则修改为 idx > 0 or idx < 0 ;


4)组合索引中,没使用第一列索引,索引失效;如select … where A and B 若 A 和 B 中有一个不包含索引,则索引失效。


5)WHERE子句:OR操作符,如果其中一个条件不可使用索引进行优化,整个条件将被认为是非索引化的。

IN子查询,如果子查询的结果不能使用索引进行匹配,那么整个IN子查询将被认为是非索引化的。


DROP TABLE IF EXISTS `index_failure_t`;
CREATE TABLE `index_failure_t` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255) DEFAULT NULL,
  `cid` INT(11) DEFAULT NULL,
  `score` SMALLINT DEFAULT 0,
  `phonenumber` VARCHAR(20),
  PRIMARY KEY (`id`),
  KEY `name_idx` (`name`, `cid`), -- 尽量使用联合索引
  KEY `phone_idx` (`cid`)
) ENGINE = innoDB AUTO_INCREMENT=0 DEFAULT CHARSET = utf8;
INSERT INTO `index_failure_t` (`name`, `cid`, `score`, `phonenumber`) 
VALUES
  ('王思思', 1001, 98, '15801100142'),
  ('张三', 1002, 95, '15801101135'),
  ('诸葛李四', 1003, 96, '15801100111');
SHOW INDEX FROM `index_failure_t`;
SELECT * FROM `index_failure_t`;
-- 1、左模糊匹配
EXPLAIN SELECT * FROM `index_failure_t` WHERE name LIKE '%思'; -- 索引失效
EXPLAIN SELECT * FROM `index_failure_t` WHERE name LIKE '诸%'; -- 右模糊匹配,索引成功
-- 2、索引参与运算
EXPLAIN SELECT * FROM `index_failure_t` WHERE LENGTH(name) = 9; -- 索引失效
EXPLAIN SELECT * FROM `index_failure_t` WHERE `id` + 1 = 3;     -- 索引失效
EXPLAIN SELECT * FROM `index_failure_t` WHERE `score` BETWEEN 95 AND 96;  -- 索引失效
EXPLAIN SELECT * FROM `index_failure_t` WHERE `id` = 3 - 1;     
--  隐式转换:mysql 遇到字符串和数字比较时,会自动将字符串转换为数字
EXPLAIN SELECT * FROM `index_failure_t` WHERE `phonenumber` = 15801100142; -- 索引失效
-- 等价于:EXPLAIN SELECT * FROM `index_failure_t` WHERE CAST(`phonenumber` AS SIGNED INT) = 15801100142; 
-- 3、where: or 非索引 | in 子查询
EXPLAIN SELECT * FROM `index_failure_t` WHERE `cid` = '1002' or `score` = 95; -- `score`没有创建索引,索引失效
EXPLAIN SELECT * FROM `index_failure_t` WHERE `cid` = '1002' or `score` BETWEEN 95 AND 96;  -- 范围查询无法索引,索引失效
EXPLAIN SELECT * FROM `index_failure_t` WHERE `cid` IN (SELECT `cid` FROM `index_failure_t` WHERE `score` > 95); -- 索引失效


四、索引优化


4.1 索引原则

1)查询频次较高且数据量大的表建立索引;索引选择使用频次较高,过滤效果好的列或者组合。


2)使用短索引;例如,如果某一列的取值范围很小,可以考虑使用较短的数据类型,如SMALLINT或TINYINT,而不是使用INT或BIGINT。


3)对于很长的动态字符串,考虑使用前缀索引;

有时候需要索引很长的字符串,这会让索引变的大且慢,通常情况下可以使用某个列开始的部分字符串,这样大大的节约索引空间,从而提高索引效率,但这会降低索引的区分度,索引的区分度是指不重复的索引值和数据表记录总数的比值。索引的区分度越高则查询效率越高,因为区分度更高的索引可以让mysql在查找的时候过滤掉更多的行。对于 BLOB , TEXT ,VARCHAR 类型的列,必要时使用前缀索引,因为mysql 不允许索引这些列的完整长度,使用该方法的诀窍在于要选择足够长的前缀以保证较高的区分度。


select count(distinct left(name,3))/count(*) as sel3,
count(distinct left(name,4))/count(*) as sel4,
count(distinct left(name,5))/count(*) as sel5,
count(distinct left(name,6))/count(*) as sel6,
from user;
alter table user add key(name(4));
-- 注意:前缀索引不能做 order by 和 group by

4)对于组合索引,考虑最左侧匹配原则和覆盖索引;


5)尽量选择区分度高的列作为索引;该列的值相同的越少越好;


6)不要 select * ; 尽量只列出需要的列字段;方便使用覆盖索引;


7)索引列,列尽量设置为非空;


8)可选:开启自适应 hash 索引或者调整 change buffer


select @@innodb_adaptive_hash_index;
set global innodb_adaptive_hash_index=1; -- 默认是开启的
select @@innodb_change_buffer_max_size;
-- 默认值为25 表示最多使用1/4的缓冲池内存空间 最大值为50
set global innodb_change_buffer_max_size=30;

4.2 SQL 优化

找到 sql 语句


  • show processlist:可以查看此时线上运行的 sql 语句;
  • 开启慢查询日志


分析 sql 语句


  • 索引 where, group by, order by
  • 优化sql 语句:in 优化成联合查询,减少联合查询


工作当中不要使用 age 字段,而是存储生日年月日

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
24天前
|
存储 关系型数据库 MySQL
阿里面试:为什么要索引?什么是MySQL索引?底层结构是什么?
尼恩是一位资深架构师,他在自己的读者交流群中分享了关于MySQL索引的重要知识点。索引是帮助MySQL高效获取数据的数据结构,主要作用包括显著提升查询速度、降低磁盘I/O次数、优化排序与分组操作以及提升复杂查询的性能。MySQL支持多种索引类型,如主键索引、唯一索引、普通索引、全文索引和空间数据索引。索引的底层数据结构主要是B+树,它能够有效支持范围查询和顺序遍历,同时保持高效的插入、删除和查找性能。尼恩还强调了索引的优缺点,并提供了多个面试题及其解答,帮助读者在面试中脱颖而出。相关资料可在公众号【技术自由圈】获取。
|
6天前
|
SQL 关系型数据库 MySQL
MySQL慢查询优化、索引优化、以及表等优化详解
本文详细介绍了MySQL优化方案,包括索引优化、SQL慢查询优化和数据库表优化,帮助提升数据库性能。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
MySQL慢查询优化、索引优化、以及表等优化详解
|
5天前
|
SQL 缓存 监控
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
本文详细解析了数据库、缓存、异步处理和Web性能优化四大策略,系统性能优化必知必备,大厂面试高频。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
|
22天前
|
存储 关系型数据库 MySQL
MySQL主从复制原理和使用
本文介绍了MySQL主从复制的基本概念、原理及其实现方法,详细讲解了一主两从的架构设计,以及三种常见的复制模式(全同步、异步、半同步)的特点与适用场景。此外,文章还提供了Spring Boot环境下配置主从复制的具体代码示例,包括数据源配置、上下文切换、路由实现及切面编程等内容,帮助读者理解如何在实际项目中实现数据库的读写分离。
MySQL主从复制原理和使用
|
14天前
|
SQL 存储 缓存
如何优化SQL查询性能?
【10月更文挑战第28天】如何优化SQL查询性能?
59 10
|
11天前
|
缓存 监控 关系型数据库
如何优化MySQL查询速度?
如何优化MySQL查询速度?【10月更文挑战第31天】
36 3
|
13天前
|
SQL 存储 缓存
SQL Server 数据太多如何优化
11种优化方案供你参考,优化 SQL Server 数据库性能得从多个方面着手,包括硬件配置、数据库结构、查询优化、索引管理、分区分表、并行处理等。通过合理的索引、查询优化、数据分区等技术,可以在数据量增大时保持较好的性能。同时,定期进行数据库维护和清理,保证数据库高效运行。
|
13天前
|
缓存 关系型数据库 MySQL
如何优化 MySQL 数据库的性能?
【10月更文挑战第28天】
37 1
|
21天前
|
NoSQL 关系型数据库 MySQL
MySQL与Redis协同作战:百万级数据统计优化实践
【10月更文挑战第21天】 在处理大规模数据集时,传统的单体数据库解决方案往往力不从心。MySQL和Redis的组合提供了一种高效的解决方案,通过将数据库操作与高速缓存相结合,可以显著提升数据处理的性能。本文将分享一次实际的优化案例,探讨如何利用MySQL和Redis共同实现百万级数据统计的优化。
54 9
|
15天前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第27天】本文深入探讨了MySQL的索引策略和查询性能调优技巧。通过介绍B-Tree索引、哈希索引和全文索引等不同类型,以及如何创建和维护索引,结合实战案例分析查询执行计划,帮助读者掌握提升查询性能的方法。定期优化索引和调整查询语句是提高数据库性能的关键。
77 1