关于MySQL优化的思考一【存储引擎、索引】

对于SQL优化,今天我们先来看下基本知识:

  • • 存储引擎,了解MySQL的不同存储引擎的特性和适用场景,可以帮助你在数据库设计和应用开发中做出明智的决策。
  • • 索引,当表没有索引时,查询数据可能是全表查询;当创建索引后,先查索引,根据索引检索到数据,提供获取数据的效率。如何建立索引至关重要。

1 存储引擎

1.1 MySQL体系结构层

  • • 连接层,该层是客户端和连接服务,主要完成类似于连接处理、授权认证等
  • • 服务层,要完成大多数核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在该层实现,如过程、函数等
  • • 引擎层,存储引擎负责数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,可以根据自己的需要,选择合适的存储引擎
  • • 存储层,将数据存储在文件系统之上,并完成与存储引擎的交互

1.2 存储引擎简介

存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎基于表,可以以表的维度来进行指定。

查看当前表的存储引擎

-- 查看建表语句,查看存储引擎,默认是InnoDB
show create table emp;

新建表时指定存储引擎

create table 表名(
  字段 字段类型 [comment 字段注释]
  ...
  
)engine=innodb [comment 表注释];

查看当前数据库支持的存储引擎

show engines;

1.3 存储引擎特点

1.3.1 InnoDB

  • 介绍

    • InnoDB是一种兼顾高可靠性和高性能的通用的存储引擎,在MySQL5.5之后,作为MySQL的默认存储引擎
  • 特点

    • DML操作(增删改)遵循ACID模型,支持事务
    • 行级锁,提高并发访问性能
    • 支持外键foreign key约束,保证数据的完整性和正确性
  • 文件

    • xxx.idb:xxx代表表名,InnoDB存储引擎的每张表都会对应这样的一个表空间文件,存储该表的表结构(frm,sdi)、数据和索引

1.3.2 MyISAM

  • 介绍

    • MyISAM是MySQL早期默认的存储引擎
  • 特点

    • 不支持事务,不支持外键

    • 支持表锁,不支持行锁

    • 访问速度快

  • 文件

    • xxx.sdl:存储表结构信息
    • xxx.MYD:存储数据
    • xxx.MYI:存储索索引

1.3.3 Memory

  • 介绍
    • Memory引擎的表数据是存储在内存中的,由于受到硬件问题、或断电问题的影响,只能将这些表作为临时表或者缓存表使用
  • 特点
    -内存存放
    • hash索引
  • 文件
    • xxx.sdi:存储表结构信息

1.4 存储引擎选择

在选择存储引擎时,需要根据应用系统的特点选择合适的存储引擎,对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合使用。

  • InnoDB:是MySQL的默认存储引擎,支持事务、外键。如果应用对事务的完整性有比较高的要求,在并发场景下要求数据一致性,数据操作除了插入和查询外,还包含很多的更新、删除操作,所有InnoDB存储引擎是好的选择
  • MyISAM:如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高。但实际场景会使用NoSQL
  • Memory:将所有的数据保存到内存中,访问速度快,通常用于临时表及缓存。缺陷是对表的大小有限制,太大的表无法缓存到内存中,而且无法保障数据的安全性。实际场景中使用NoSQL(Redis)

2 索引

索引(index),是帮助MySQL高效获取数据的数据结构(有序)。当表没有索引时,查询数据可能是全表查询;当创建索引后,先查索引,根据索引检索到数据,提供获取数据的效率。

2.1 索引的优缺点

优点 缺点
提高数据检索的效率,降低数据库的IO成本 索引列也要占用空间
通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗 索引大大提高了查询效率,同时也降低更新表的速度,如对表进行insert、update、delete时,效率降低

2.2 索引结构

MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的结构。

  • B+Tree索引,是常见的索引类型,大部分存储引擎都支持B+树索引,如常见的InnoDB、MyISAM、Memory存储引擎
  • Hash索引,底层数据结构是用哈希表实现的,只有精确匹配索引的查询才有效,不支持范围查询
  • R-tree(空间索引),空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,很少用
  • Full-tree(全文索引),是一种通过建立倒排索引,快速匹配文档的方式,类似于Lucene、Solr、ES

二叉树数据结构

左侧的叶子节点小于父节点

二叉树在顺序插入时,会形成一个链表,查询性能大大降低,大数据量情况下,层级较深,检索速度慢。红黑树,平衡二叉树,可以解决形成链表,但是仍然有大数据量情况下,层级较深,检索速度较慢的问题。

B-Tree(多路平衡查找树) 以一棵最大度数(max-degree)为5(5阶)的b-Tree为例(每个节点最多存储4个key,5个指针),可在https://www.cs.usfca.edu/\~galles/visualization/BTree.html上查看B-Tree数据结构

B+Tree的特点

  • 所有的元素都会出现在叶子节点
  • 上边的分页节点,只起到索引的作用,叶子节点用来存放数据
  • 所有的叶子节点形成了一个单向链表

以一棵最大度数为4的B+Tree为例,

可在https://www.cs.usfca.edu/\~galles/visualization/BPlusTree.html上查看数据结构

MySQL索引数据结构对经典的B+Tree进行了优化,在原来B+Tree的基础上,增加了一个指向相邻子节点的链表指针,就形成了带有顺序的B+Tree,提高区间访问的性能。

Hash结构,哈希索引就是采用一定的hash算法,将键值换成新的hash值,映射到对应的槽位上,然后存储在hash表中

  • hash索引只能用于对等比较(=,in),不支持范围查询(between,>,<,...)
  • 无法利用索引完成排序操作
  • 查询效率高,通常只需要一次检索就可以了,效率通常要高于B+Tree索引

为什么InnoDB存储引擎使用B+Tree索引结构

  • 相对于二叉树,层级更少,搜索效率高
  • 对于B-Tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针也就减少,要同样保存大量数据,只能增加树的高度,导致性能降低
  • 相对于Hash索引来说,B+Tree支持范围匹配和排序操作

2.3 索引分类

  • 主键索引,针对表中的主键建立的索引,只能有一个,primary key
  • 唯一索引,可以有多个,unique
  • 常规索引,可以有多个,快速定位特定数据
  • 全文索引,全文检索查找的是文本中的关键词,而不是比较索引中的值,可以有多个,fulltext

在InnoDB中的分类

  • 聚集索引(Clustered index),将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据,必须有,且只有一个
  • 二级索引(Second Index),将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键,可以存在多个

聚集索引的选取规则

  • 如果存在主键,主键索引就是聚集索引
  • 如果没有主键,将使用第一个唯一索引作为聚集索引
  • 如果没有主键,也没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引

聚集索引和二级索引的区别

  • 聚集索引下挂的是行数据
  • 二级索引下挂的是数据的id

回表查询

  • 二级索引进行查询到ID
  • 通过ID进行聚集索引查询到对应的数据

如下示例:

-- user 表中,id为主键,并且给name增加了索引

-- 该查询,可直接使用聚集索引(id为主键)进行查询

select * from user where id = 10;

-- 该查询,需要先使用二级索引,然后回表,使用聚集索引查询

select * from user where name = '小明';

InnoDB主键索引的B+Tree高度为多高?

假设,一行数据大小为1k,一页中可以存储16行这样的数据,InnoDB的指针占用6个字节的空间,主键即使为bigint,占用字节数为8。由于每页的大小为16K。

  • 高度为2:即存储的行数据为117116 = 18736,n*8+(n+1)6=161024,算出的n约为1170
    • 即存储的行数据为1171*16 = 18736
  • 高度为3:1171117116=21939856
  • 如果表中数据超过几千万了,就需要考虑分库分表等优化方式了

2.4 索引语法

创建索引

create [unique|fulltext] index index_name on table_name(字段1,字段2,...); 

查看索引

show index from table_name;

删除索引

drop index index_name on table_name;

-- 创建数据库
CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `age` int NOT NULL,
  `city` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

-- 创建索引
create index index_name on user(name);

-- 查看索引
show index from user;

-- 创建索引
create index index_name_age_city on user(name,age,city);

-- 删除索引
drop index index_name_age_city on user;

2.5 索引使用法则

2.5.1 最左前缀法则

如果索引了多列(联合索引),要遵循最左前缀法则。最左前缀法则指:查询从索引的最左列开始,并不跳过索引中的列。如果跳跃某一列,索引将部分失效(后面的字段索引失效)

比如在user表中,创建的索引是id+name+age

  • 可以使用到索引的情况
    • 需要查询的列为id
    • 需要查询的列为id+name
  • 不能使用多索引的情况
    • 需要查询的列为name+age

注意:经测试,发现MySQL8.0的版本上索引都没有失效!!!

2.5.2 范围查询

联合索引总,出现范围查询(<,>),范围查询右侧的列索引失效

-- 即city的这个列索引会失效
explain select * from user where name='夏明' and age > 30 and city ='北京';

-- 在>=、<=的查询中,city的列索引不会失效
explain select * from user where name='夏明' and age >= 30 and city ='北京';

2.5.3 索引列上运算会索引失效

不要在索引列上进行运算操作,索引将失效

create index index_age on user(age);


explain select * from user where substr(user.age,1,1)='3';

2.5.4 字符串不加引号会索引失效

字符串类型字段使用时,不加引号,索引将失效

create index index_age on user(name,age,status);

-- 可以使用索引
explain select * from test.user where name='夏明' and age = 30 and status ='1';

-- 部分索引失效
explain select * from test.user where name='夏明' and age = 30 and status =1;

2.5.5 模糊查询

如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配的话,索引会失效。

create index index_name on test.user(name);

explain select * from test.user where user.name like '张%';

-- 索引失效
explain select * from test.user where user.name like '%三';

-- 索引失效
explain select * from test.user where user.name like '%三%';

2.5.6 or连接的条件

用or分割开的查询条件,如果or的一侧没有索引(前边有后边没有;前边没有后边有),那索引会失效

-- 只有id为索引,使用or连接查询时,索引失效
explain select * from test.user where id = 1 or name='夏明';

2.5.7 数据分布影响

如果MySQL评估使用所有会比全表扫描还慢,则不会使用索引

create index index_age on test.user(age);

-- 由于所有的数据中age大于10,
explain select * from test.user where age >= 10;


-- 修改条件后,就使用到了索引
explain select * from test.user where age >= 30;

2.5.8 给引擎提示

提示存储引擎使用哪个索引:

  • use index

  • ignore index

  • force index

    -- 创建2个索引
    create index index_name_age_city on user(name,age,city);

    create index index_name on user(name);

    explain select * from test.user where name='夏明';

    -- 建议使用索引
    explain select * from test.user use index (index_name) where name='夏明';
    -- 忽略使用索引
    explain select * from test.user ignore index (index_name) where name='夏明';
    -- 强制使用索引
    explain select * from test.user force index (index_name) where name='夏明';

2.5.9 覆盖索引

尽量使用覆盖索引(查询使用了索引,并且查询的字段包含索引字段),减少使用select(*) Extra中的信息

  • using index condition:查询中使用了索引,但是需要回表查询数据
  • using where; using index:查询中使用了索引,并且查找的字段在索引上都有,不需要回表查询数据

2.5.10 单列索引和联合索引

  • 单列索引:即一个索引只包含单个列
  • 联合索引:即一个索引包含了多个列

实际的业务场景中,如果存在多个查询,考虑针对于查询字段建立索引时,建议建立联合索引 多条件查询时,MySQL优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询

2.6 索引设计原则

  1. 针对于数据量较大,且查询比较频繁的表建立索引
  2. 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
  3. 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高
  4. 如果是字符串类型的的字段,字段的长度很长,可以针对字段的特点,建立前缀索引
  5. 尽量使用联合索引,减少单例索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询速度
  6. 要控制索引的数量,索引并不是越多越好,索引越多,维护索引结构的代价就越大,会影响增删改的效率
  7. 如果索引列不能存储null值,在创建表的时候将就使用not null来约束。当优化器知道每列是否包含null值时,它可以更好的确定那个索引最有效
相关推荐
高兴就好(石1 小时前
DB-GPT部署和试用
数据库·gpt
这孩子叫逆2 小时前
6. 什么是MySQL的事务?如何在Java中使用Connection接口管理事务?
数据库·mysql
Karoku0662 小时前
【网站架构部署与优化】web服务与http协议
linux·运维·服务器·数据库·http·架构
码农郁郁久居人下2 小时前
Redis的配置与优化
数据库·redis·缓存
MuseLss3 小时前
Mycat搭建分库分表
数据库·mycat
Hsu_kk4 小时前
Redis 主从复制配置教程
数据库·redis·缓存
DieSnowK4 小时前
[Redis][环境配置]详细讲解
数据库·redis·分布式·缓存·环境配置·新手向·详细讲解
程序猿小D4 小时前
第二百三十五节 JPA教程 - JPA Lob列示例
java·数据库·windows·oracle·jdk·jpa
Flerken1014 小时前
数据库语言、SQL语言、数据库系统提供的两种语言
数据库·sql·oracle
掘根4 小时前
【网络】高级IO——poll版本TCP服务器
网络·数据库·sql·网络协议·tcp/ip·mysql·网络安全