MySQL高阶篇-数据库优化

一 索引基本篇

1.1 没有索引的情况

数据库没有索引的情况下,数据分布在硬盘不同的位置上面,读取数据时,摆臂需要前后摆动查找数据,这样操作非常耗时。如果数据顺序摆放,也需要按顺序读取,这样就相当于进行了 多次 I/O 操作,依旧非常耗时。如果不借助任何索引结构来帮助快速定位数据 CPU 必须先去磁盘查找这条记录,找到之后加载到内存,再对数据进行处理。这个过程最耗时间的就是磁盘 I/0

假如给数据使用二叉树这样的数据结构进行存储

对字段添加索引,就相当于在磁盘上为 字段 维护了一个索引的数据结构,即一个二叉搜索树。二叉搜索树的每个节点存储的是(K, V) 结构,key 是 字段值,value 是该 key 所在行的文件指针(地址)可以发现,只需要查找二叉树的层数次,就可以定位到记录的地址,查询速度也就提高了。

使用索引的原因,是为了减少磁盘 I/O 的次数,加快查询的速度

1.2 什么是索引

MySQL 官方对索引的定义为:索引(Index)是帮助 MySQL 高效获取数据的数据结构。

  • 索引是数据结构
    • 可以简单理解为是一个"已经排好序的,能进行快速查找的数据结构",满足特定查找算法。这些数据结构以某种方式指向数据, 这样就可以在这些数据结构的基础上实现高级查找算法。
  • 索引也是文件

​ 为了防止服务器宕机而丢失数据,索引也会以文件的形式持久化到磁盘。

1.3 索引的作用

  • 快速的查找数据。
    • 如果没有索引,通常会全表扫描数据,如果表的数据非常大的话,一条一条的去匹配的话,最坏的情况下需要匹配O(n)最坏时间复杂度;而通过索引的话,不需要全表扫描,一般只需要O(logn)次就可以定位到具体的数据,大大减少了查询速度。
  • 管理数据库约束。
    • 索引通常还会用于数据库约束,例如:UNIQUE,PRIMARY KEY,FOREIG KEY,当一个索引被定义成UNIQUE时,数据库同时创建一个隐式的约束。

1.4 索引的优缺点

  1. 优点:
    • 减少I/O次数,加快检索速度;
    • 根据索引分组和排序,可以加快分组和排序。
  2. 缺点
    • 创建索引和维护索引耗时,时间随着数据的增加而增加,成正比;
    • 索引需要占物理空间,除了数据表占数据空间外,每一个索引还要占一定的物理空间,如果建立聚簇索引,占得物理空间会更大;
    • 索引会降低数据表的修改操作(删除,添加,修改)的效率,因为在修改数据表的同时还需要修改索引表

1.5 索引的分类

1.5.1 从物理存储结构分:

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

    • 聚集索引中的叶子节点存储的除了索引的Key之外,value是真正的行数据。

    • InnoDB存储引擎的表一定会有一个聚集索引,

    • 默认情况下,主键索引就是聚集索引,如果该表没有主键索引,使用第一个非空的唯一索引作为聚集索引,如果都没有,InnoDB会给每条记录上添加一个隐式的自增字段row_id,创建聚簇索引。

  2. 非聚集索引

    • 非聚集索引中的叶子节点存储的除了索引的Key之外,value是行数据的地址指针。

    • 除了聚集索引之外,全都是非聚集索引,包括该表的各种索引(唯一索引,二级索引,普通索引)

    • 在MyISAM引擎中所有的索引(包括主键索引)都是非聚集索引。 即没有聚簇索引

1.5.2 从逻辑功能分

  1. 主键索引:主键列上默认创建的索引,唯一标识表中的每行数据。
  2. 唯一索引:确保索引列的每个值都是唯一的。
  3. 普通索引(Normal Index):用于加速查询。
  4. 全文索引(Fulltext Index):用于增强搜索文本数据的能力。
  5. 组合索引:包含多个列的索引。
  6. 空间索引(Spatial Index):应用于空间数据类型,如GEOMETRY

1.5.3 从字段唯一性分

  1. 单列索引:一个索引只包含一个列。
  2. 复合索引:一个索引包含多个列。

1.5.4 从索引的数据结构分:

  1. B+Tree索引:MySQL大多数索引类型都是B+Tree索引,适合范围查询和顺序扫描。
  2. Hash索引:只支持等值比较的快速查找,不支持范围查询。
sql 复制代码
1. Hash索引是辅助索引,用来辅助B+Tree索引
2. Hash索引是基于Hash表实现的,对索引列进行hash算法得到hash码,作为索引的key。
  value是具体行数据的地址指针。
3. 时间复杂度是o(1),一般用于精确查找
4. Hash索引不支持排序,因为本身是无序的
5. MySQL8.0不再支持Hash索引,即使创建也是无效
-- 建表时指定
CREATE TABLE my_table (
    id INT PRIMARY KEY,
    data VARCHAR(255),
    INDEX my_hash_index USING HASH (data)
);
--建表后:
ALTER TABLE my_table ADD INDEX hash_index USING HASH (data);

1.6 索引的创建

1.6.1 三种创建方式

  • 建表时创建
  • 使用 alter创建
  • 使用create index创建

1.6.2 案例演示

在mysql8.0里使用create clustered index 报错

在MySQL 8.0中,不支持使用CREATE CLUSTERED INDEX语句来创建聚集索引。MySQL中的聚集索引是根据InnoDB存储引擎的实现来定的,用户不能手动指定聚集索引。

InnoDB表的数据存储是按照主键排序的(如果表定义时有显式主键,则按该主键排序;如果没有显式主键,则选择一个唯一的非空索引代替,如果也没有,则InnoDB会隐式定义一个主键,通常是6个字节的ROWID)。因此,通常不需要手动创建聚集索引,因为InnoDB已经为你做了这个工作。

如果你的目的是创建一个索引用于优化查询性能,你应该使用标准的CREATE INDEX语句:

1)主键索引的创建

创建主键约束时,数据库就会自动维护一个主键索引,一般情况下也是聚集索引,

注意:如果是alter语法添加的主键索引,是非聚集索引。因为建表时一定会有一个聚集索引。

sql 复制代码
-- 列级
CREATE TABLE my_table (
    id INT PRIMARY KEY,
    column1 VARCHAR(50)
);
-- 表级
CREATE TABLE my_table (
    id INT NOT NULL,
    column1 VARCHAR(50),
    PRIMARY KEY (id)
);
 
-- 或者如果表已经存在,你可以使用以下命令来创建主键索引:
ALTER TABLE table_name ADD PRIMARY KEY (id);

2)唯一索引的创建

sql 复制代码
-- 创建表时,列级
create table table_name(
   id int, 
   column1 varchar(30) unique,
        column2 ....
);
-- 创建表时,表级
create table table_name(
   id int primary key, 
   column1 varchar(30), 
   column2  ...,
   unique(name)
);
-- 建表后
alter table table_name add unique(name);

-- create方法
create unique index idx_name on table_name(column1)

3)普通索引的创建

sql 复制代码
 -- 创建表的最后,指定某列为索引
create table table_name(
   id int, 
   column1 varchar(30),
   column2 ....,
   index idx_name(id)          
);
 
 -- 创建完表以后指定某列为普通索引
 alter table table_name add index idx_name(name);

 -- 创建一个索引名为 idx_name 的索引
 create index idx_name on table_name(name);

4)全文索引的创建

MySQL的低版本的innoDB引擎不支持全文索引,只有MyISAM引擎支持全文索引;

MySQL5.7版本后innoDB引擎也支持了全文索引

sql 复制代码
-- 建表时
create table table_name(
        id int,
        name varchar(20),
        fulltext index index_name(name) 
) 
-- 建表后
ALTER TABLE table_name ADD FULLTEXT INDEX index_name(column1);

**注意:**全文索引适用于对文本内容进行搜索的场景,如文章、书籍、网页等。它主要用于快速查找文本中的关键词或短语。

5)组合索引的创建

组合索引就是创建索引时指定的字段不止一个。

sql 复制代码
ALTER TABLE table_name ADD PRIMARY KEY (id,...);
alter table table_name add unique(name,...);
create index idx_name on table_name(name,....);
ALTER TABLE table_name ADD FULLTEXT INDEX index_name(column1,...);

6)空间索引的创建

在MySQL8.0以前,只有MyISAM引擎支持空间索引,MySQL8.0开始,innoDB引擎也支持空间索引了 要求:空间类型的字段必须为非空, 关键字是SPATIAL。

sql 复制代码
CREATE TABLE t5(
   g GEOMETRY NOT NULL,
   SPATIAL INDEX spatIdx(g)
) ENGINE = MyISAM;

SHOW CREATE TABLE t5;

1.6.3 MySQL创建索引的原则

  1. 最左前缀匹配原则

    • 非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配。

    • 比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

  2. =和in可以乱序

    比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式

  3. 尽量选择区分度高的列作为索引

    区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条记录

  4. 索引列不能参与计算

    保持列"干净",比如from_unixtime(create_time) = '2014-05-29'就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成create_time = unix_timestamp('2014-05-29');

  5. 尽量的扩展索引,不要新建索引。

    比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可

1.7 索引的查看

方式1: 查看某一张表的所有索引。包括索引名称、索引所在的列、索引类型等

sql 复制代码
show keys from table_name;
show index from table_name;

方式2:通过INFORMATION_SCHEMA查询:MySQL提供了一个特殊的数据库INFORMATION_SCHEMA,其中包含了关于数据库、表、列、索引等信息的元数据。

sql 复制代码
SELECT *
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = 'database_name' AND TABLE_NAME = 'table_name';

database_name替换为你要查询的数据库名称,将table_name替换为你要查询的表名。这条语句将返回指定表的所有索引信息,包括索引名称、索引所在的列、索引类型等。

比如:查看mydb库的emp表的所有索引

sql 复制代码
SELECT *
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = 'mydb' AND TABLE_NAME = 'emp';

1.8 删除索引

1.8.1 删除索引的意义

在索引不再需要或对性能产生负面影响时,删除索引可以释放存储空间并减少插入、更新和删除操作的开销。

  • 性能优化:过多的索引会增加写操作的开销,导致性能下降。
  • 存储空间:索引会占用额外的存储空间,删除不必要的索引可以节省存储资源。
  • 维护成本:索引越多,维护成本越高,删除不必要的索引可以降低维护成本。

扩展知识: mysql并没有规定一个表的索引数量上限,我们只能说,有些低效的索引没有多大用,可以删除,有些高效的索引可以留着,高效的索引视表的结构和数据体量而定,并非网上说的不能超过5个。

1.8.2 如何删除索引

删除索引的步骤相对简单,但每一步都需要仔细考虑,以确保操作的安全性和有效性。

步骤1: 确定需要删除的索引

首先,你需要确定哪些索引需要删除。通常,选择那些不再使用的索引或对性能产生负面影响的索引。

sql 复制代码
示例:-- 查看表的索引
SHOW INDEX FROM employees;

步骤2:评估删除索引的影响

在删除索引之前,评估删除索引对查询性能的影响。可以通过 EXPLAIN 语句查看查询计划,确保删除索引不会导致性能下降。

示例

sql 复制代码
-- 使用 EXPLAIN 语句查看查询计划
EXPLAIN SELECT * FROM employees WHERE department_id = 1;

步骤3:备份数据

在执行删除索引操作之前,建议备份相关数据,以防万一出现问题可以恢复。

示例

sql 复制代码
-- 备份数据
CREATE TABLE employees_backup AS SELECT * FROM employees;

步骤4:删除索引

使用 DROP INDEX 语句删除索引。可以在表上直接删除索引,也可以在视图上删除索引。

示例

sql 复制代码
-- 删除索引
drop index index_name on table_name ;

alter table table_name drop index index_name ;
alter table table_name drop primary key ;

步骤5:验证删除效果

删除索引后,可以通过 SHOW INDEX 语句验证索引是否已被成功删除,并通过 EXPLAIN 语句再次查看查询计划,确保性能没有下降。

示例

sql 复制代码
-- 验证索引是否已被删除
SHOW INDEX FROM employees;

-- 再次查看查询计划
EXPLAIN SELECT * FROM employees WHERE department_id = 1;

二 索引进阶篇

2.1 数据库索引的存储结构的发展历程

1)数组和链表的选择

sql 复制代码
数组的特性: 查找快、但是插入、修改数据慢。
链表的特性: 查找慢、插入、修改快。

都不完美。退而求其次,选择容易完善的,而数组在插入时涉及到元素的移动,而移动的过程中,不能进行查询,查询可能会出错。影响太大,选择链表

2)从链表到二叉树

sql 复制代码
链表查询速度慢,需要解决

使用二分法查找,衍生出二叉查找树结构

3)从二叉查找树到平衡二叉树

SQL 复制代码
二叉树高度不可控、容易出现线性结果,又回到了链表。

衍生出了平衡二叉树:  平衡二叉树通常会保证树的左右两边的节点层级相差不会大于2

4)从平衡二叉树到B树

发现影响查询效率的决定性因素,是树的高度。只要树的层级越少,那么树的查询效率就越高

本着这个原则我们就思考每个节点能不能多存点 数据,只要每个节点的数据保存的越多,那么我们树的层级就会越少,

B树相对平衡二叉树最大的一个改变,就是B树的每个节点可存储的key增多了,特别是在B树应用到数据库中的时候,节点存储关键字的数量充分利用了磁盘块IO的原理(磁盘数据存储是采用块的形式存储的,每个块的大小为4K,每次IO进行数据读取时,同一个磁盘块的数据可以一次性读取出来),B树只要把节点大小限制在磁盘块大小范围,这样就可以只需要一次IO就读取到节点所有数据,节点存储了更多的关键字,但是并不会影响IO的次数。B树树相对于之前节点可以存储更多的关键字,所以树的层级会比原来少很多,树的层级减少了,那么检索的效率就会大大提升。

2.2 B+Tree

2.2.1 BTREE的优化空间

其实B树已经接近我们的理想预期了,但是还是能从B树的上面找到可以优化的地方,比如以下几个方面:

  1. B树的节点同时保存了索引的key和数据值,如果节点只保存索引key不保存值,那么索引树的层级会不会更进一步的减少
  2. B树每个节点保存了数据值,查询不同的数据效率就会显得不稳定,有些在树的第一层匹配成功就返回,有些则可能需要匹配到树的最后一层才返回。
  3. 如果要查询所有数据,那么就必须遍历整个B树的每个节点,效率还是不够高

2.2.2 B+TREE

B+树继承了B树的所有特点,并在这基础上做了一些优化,B+树主要做了下面几点的优化。

第一点:B+树的非叶子节点不再保存实际的数据,只保存索引key,而所有的数据都保存到叶子节点中

好处如下:

  1. 树层级变少了

    非叶子节点不再保存实际数据(地址指针),只保存索引的key,则相对于B树来说,B+树的每个非叶子节点存储的索引key会更多,树的层级就变少了,那么查询效率也会更快。

  2. 查询更稳定:因为B+树所有数据值(地址指针)都是存在叶子节点上,每次查找的次数都相同,因此查询速度要比B树更稳定

  3. 遍历整个树更快:

第二点: B+树叶子节点的关键字从小到大有序排列,每个叶子最后都会保存右边叶子的地址指针,构成有序链表

好处如下:

-1) 遍历整个树更快:

B+树遍历整棵树只需要遍历所有的叶子节点即可。而不需要像B树一样需要对每一层进行遍历,这有利于数据库做全表扫描。

-2) 排序和范围查询更方便: 因为B+树的所有叶子节点构成了一个有序链表,这样在进行数据排序和询范围大小查询数据的时候更方便,数据紧密性也更高。

2.2.3 B+索引树的层级

为什么说Mysql的索引树一般都在1-3层的结构

经常听到别人说Mysql的索引树一般会在3层,这个是有什么依据? 其实这个的确是有数据计算支撑的,我们可以根据B+树的原理进行一下数据推算,因为磁盘每页数据为4K,而Mysql的B+树对此又进行了一次调整,在Mysql也有自己的页概念,Mysql里的每一页数据等于磁盘4个页的大小,所以在Mysql里面的一页数据其实是16K,那么也就意味着Mysql里B+树的非叶子节点可存储16K的数据。

根据一个计算我们可以基本得出,类型varchar,长度为10,字符类型为utf8mb4的索引字段,数据在512条之内树结构只有一层。数据在262144之内树只有两层,数据在134217728之内,索引树都会保持在3层之内,而我们的表数据一般而言都保持在千万级以内,所以说Mysql的索引树一般都在1-3层。

2.2.4 B+tree与Btree的主要区别

  1. 数据的存储位置不同
    B+Tree中的数据(行数据或者地址指针)只会存储在叶子节点上,而B-tree的数据(地址指针)会存储在各个节点 上;
  2. B+Tree的层级更少
  3. 因为B+Tree的非叶节点中不存储data,每个非叶子节点存储的key就更多,树的层级就更少。而B-tree的非叶节点中也会存储数据,导致非叶子存储的key更少,树的层级就更多。
  4. B+Tree的查询速度更稳定
  5. B+Tree中所有的查询都要查询到叶子节点上才能获取到数据,而叶子节点的高度都是相同的,因此所有数据的查询速度都是一样的,查询的时间复杂度都是固定为 log n。而B-tree的数据存放在各个节点上,查询时间复杂度不固定,可能查询第一个节点就能获取想要的数据,也可能查询到最后一个节点才获取到想要的数据,因此查询的时间复杂度最快为O(1),最坏为O(n);
  6. B+Tree叶子节点是有序双向链表。遍历和范围查找更快
  7. B+Tree叶子节点两两相连可以构成了一个有序链表,大大增加区间访问性,可使用在范围查询等,而B-tree每个节点 key 和 data 在一起,则无法区间查找;
  8. B+Tree全节点遍历更快,B+Tree遍历整棵树只需要遍历所有的叶子节点即可,而B-tree需要对每一层进行遍历,因此B+Tree更有利于数据库做全表扫描。

2.3 数据页page

数据库表中的记录都是按 (row)进行存放的,每行记录也有自己的存储结构(这里不展开叙述)。但是数据库在读操作上,并不是以行为单位进行读取。因为读取一次,就是一次磁盘IO(寻址、定位、传输)。以行为单位,来处理一行数据,效率太低太低了。

之前咱们说过,磁盘IO最低一次读取4k的数据,因为文件在磁盘上是以多个block形式存储的,block的大小就是4K。

而MySQL在使用B+TREE这个数据结构时,又在磁盘IO上做了一次调整,即每次磁盘IO连续读取4个block,也就是16K 。因此MySQL实际上是以16K的字节大小为单位来读取数据到内存的。 这个单位也被称之为数据页

总结来说,MySQL以数据页为单位来读取数据,进行一次磁盘IO操作。这样的好处是大大降低了磁盘IO上的次数,减少了花在磁盘寻址,定位上的时间。

SQL 复制代码
数据页,你也可以比喻成书籍的页。
假设1条记录占用512字节,那么16K,可以存储32条记录。  
就相当于一页纸张上有32行文字。

换算下来:
                1MB可以存储2048行记录
                1GB可以存储2,097,152   >  209万行
                5GB就可以存储千万行记录了
即使1条记录占用1K字节, 那么10GB也能存储千万级别的数据表了。

2.4 InnoDB引擎的索引

2.4.1 聚集索引

数据页,是InnoDB存储引擎磁盘管理的最小单元。InnoDB存储引擎在使用B+TREE这个数据结构时,一个数据页存储一个节点的数据。

不管一个数据表是否有索引,InnoDB存储引擎默认都会为这张表维护一个B+TREE结构的索引,这个特殊的索引被称为聚集索引,也有人叫聚簇索引。

聚簇索引的产生过程:

  • 在创建表的时候,如果使用primary key指定了主键约束,那么也会产生主键索引,InnoDB就会将该主键索引视为聚簇索引
  • 如果表没有设置主键,那么 InnoDB 会使用第一个设置了非空约束的唯一索引(unique),作为聚簇索引。
  • 如果上面两个都没有的情况下,InnoDB会自动产生一个隐藏的自增row_id,并将其设置为聚簇索引。

聚簇索引的特点如下:

  1. 叶子节点上存储的是除了索引的Key之外,还有真正的行数据
  2. 行数据在磁盘上只保存一份,因此聚簇索引只有一个,也就是说,一个表只有一个聚簇索引
  3. 非叶子节点上记录的下一层的节点里的最小的key以及下一层的地址指针

2.4.2 非聚集索引

除了聚集索引外的索引,都是非聚集索引,也可以叫辅助索引,二级索引等。注意,这些索引的数据结构默认也都是B+TREE

非聚集索引的特点如下:

  • 叶子节点上存储的除了自己的Key之外,data部分存储的不是真正的行数据,而是聚集索引的key。
  • 其他非叶子节点上的存储形式和聚集索引的非叶子节点上的存储形式都是一样的。
  • select id,name,age from t where name = 'wangdaliang';
  • 如果某一个查询语句命中了二级索引,但是要查询的数据不仅仅是其主键值时(即包含其他字段的数据),那么在二级索引里找到对应的主键值后,还需要去聚集索引里找具体的行数据,这个过程就叫回表。 也就是说要查询两个B+TREE。
  • 如果只需要查询主键值,则不需要回表了,查到主键值,即返回。只需要查询一个B+TREE。

2.5 MyISAM引擎的索引

MySQL的MyISAM 引擎的表的所有的索引都是非聚集索引(非聚簇索引),该引擎也是使用了B+TREE的数据结构。不过叶子节点上存储的都是具体的行记录的地址指针。通过指针找到磁盘上的具体数据。

下面借用网上的图片来体现下:

MyISM的主键索引:

MyISAM的辅助索引:

相关推荐
舒一笑25 分钟前
如何优雅统计知识库文件个数与子集下不同文件夹文件个数
后端·mysql·程序员
鼠鼠我捏,要死了捏1 小时前
生产环境MongoDB分片策略优化与故障排查实战经验分享
数据库·mongodb·分片
KaiwuDB1 小时前
KWDB 分布式架构探究——数据分布与特性
数据库·分布式
笨蛋不要掉眼泪2 小时前
Spring Boot集成腾讯云人脸识别实现智能小区门禁系统
java·数据库·spring boot
小苏兮2 小时前
【数据结构】树与二叉树:结构、性质与存储
数据结构
你的电影很有趣3 小时前
lesson44:Redis 数据库全解析:从数据类型到高级应用
数据库·redis·缓存
NineData3 小时前
2025 DTCC大会来了,NineData联合创始人周振兴将分享《AI重塑数据库管理模式》的主题演讲
数据库
NineData3 小时前
NineData亮相2025中国数据库技术大会,并荣获《年度优秀技术团队奖》
数据库
NightDW3 小时前
连续周更任务模块的设计与实现
java·后端·mysql