对于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 索引设计原则
- 针对于数据量较大,且查询比较频繁的表建立索引
- 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
- 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高
- 如果是字符串类型的的字段,字段的长度很长,可以针对字段的特点,建立前缀索引
- 尽量使用联合索引,减少单例索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询速度
- 要控制索引的数量,索引并不是越多越好,索引越多,维护索引结构的代价就越大,会影响增删改的效率
- 如果索引列不能存储null值,在创建表的时候将就使用not null来约束。当优化器知道每列是否包含null值时,它可以更好的确定那个索引最有效