07MySQL存储引擎与索引优化
目录
MYSQL体系结构
存储引擎
常用的几种存储引擎
1.InnoDB引擎
2.MyISAM引擎
3.Memory引擎
存储引擎选择
索引
索引结构
B-Tree(多路平衡查找树)
B+Tree
hash
为什么 InnoDB存储引擎选择使用B+ tree索引结构?
索引分类
索引语法
SQL性能分析
查看执行频次
慢查询日志
Show profiles
explain执行计划
索引使用规则
最左前缀法则
范围查询
索引失效
SQL提示
覆盖索引&回表查询
什么是回表查询
思考
前缀索引
单列索引和联合索引
索引设计原则
MYSQL体系结构
mysql体系结构从上到下分为连接层,服务层,引擎层,存储层,如下图:
连接层:最上层是一些客户端和链接服务,主要完成一些连接处理,授权认证,及相关的安全方案,服务器也会为安全接入的每个客户端验证它所具备的操作权限。
服务层:主要完成大部分核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行,所有跨存储引擎的功能也在这一层实现,如过程,函数等。
引擎层:真正的负责了mysql中数据的存储和获取,服务器通过API和存储引擎进行通信,不同的存储引擎具有不同的功能,这样我么可以根据自己的需求,来选择合适的存储引擎。
数据层:主要将数据存储在文件系统上,并完成与存储引擎的交互。
存储引擎
存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表的,而不是基于库的,所以存储引擎也可被称为表类型。
如何查看一张表的引擎:
show create table 表名;
查看engine=???
建表默认是InnoDB引擎,可以指定引擎
查看当前数据库支持存储引擎:
show engines;
常用的几种存储引擎
1.InnoDB引擎
InnoDB是一种兼顾高可靠性和靠性能的通用存储引擎,在mysql 5.5版本之后,InnoDB是默认的存储引擎。
特点:DML操作遵循ACID模型,支持事务;支持行级锁,提高并发性能;支持外键foreign key约束,保证数据的完整性。
文件:InnoDB引擎的每张表都会对应一个以ibd结尾的表空间文件,该文件中存储了表的结构,数据和索引,可用innodb_file_per_table参数来控制。
逻辑存储结构如下图:
2.MyISAM引擎
MyISAM是mysql早期默认的存储引擎。
特点:不支持事务;不支持外键;支持表锁,不支持行锁;访问速度快。
文件:sdi文件存储表结构信息,MYD文件存储数据,MYI文件存储引擎。
3.Memory引擎
Memory引擎的表数据存储在内存中,由于受到硬件问题,或断点问题的影响,只能将这些表作为临时表或缓存作用。
特点:内存存放;支持hash索引(默认);
文件:sdi文件存储表结构信息;
存储引擎选择
以上三种的存储引擎特点如下图:
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。
InnoDB:是Mysql的默认存储引擎,支持事务、外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,那么InnoDB存储引擎是比较合适的选择。
MySAM:如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。
MEMORY:将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。MEMORY的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性。
总结:
- InnoDB 适合需事务保障、高并发写或依赖外键的场景(如电商订单、金融转账),是多数核心业务的首选;
- MyISAM 适合读多写少、无事务需求的静态场景(如官网文章、日志统计),优势是读性能优;
- MEMORY 适合临时缓存、高频临时查询场景(如秒杀临时库存、会话数据),速度快但数据不持久,仅存临时数据。
索引
索引是帮助mysql高效获取数据的数据结构,这样可以在这些数据结构上实现高级查询算法,例子如下图:
备注:上述二次树索引结构的只是一个示意图,并不是真实的索引结构
优势:提高数据检索的效率,降低数据库的IO成本;通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗。
劣势:索引列也是要占用空间;索引大大提高了查询效率,但是却降低了更新效率,如对表进行insert,update,delete时,效率降低。
索引结构
mysql的索引是在存储引擎层实现的,不同的存储引擎有不同的结构,主要有以下几种:
不同存储引擎支持的数据结构如下图:
B-Tree(多路平衡查找树)
以一颗最大度数(max-degree)为5(5阶)的b-tree为例(每个节点最多存储4个 key,5个指针):
--度:树的度数指的是一个节点的子节点个数。
B+Tree
B+tree是特殊的Btree,就是所有数据都存储在叶子节点,其他节点不存储数据,只存储检索用的索引值,并且在叶子节点存在单向链表,可以提高遍历速度,以4阶B+tree为例如下图:
hash
hash索引就是通过一定的hash计算,将键值换算成hash值,映射到对应的槽位,然后存储在hash表中。如果多个键值映射到同一个槽位上,就成为hash冲突,可以通过链表来解决,示例如下图:
--特点:hash索引只持支对等比较,不支持范围查询;无法利用该索引完成排序操作;查询效率高,通常只需要一次检索,效率通常高于B+tree索引。
--在mysql中,支持hash索引的是memory引擎,而InnoDB中具有自适应的hash功能,hash索引是存储引擎根据B+tree索引在指定条件下自动构建的。
为什么 InnoDB存储引擎选择使用B+ tree索引结构?
相对于二叉树,层级更少,搜索效率高;
页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低;
相对Hash索引,B+tree支持范围匹配及排序操作;
索引分类
按照约束分类,mysql的索引通常分为以下四类:
--InnoDB引擎中,根据索引的存储形式可以分为以下几类:
聚集索引选取规则:
如果存在主键,主键索引就是聚集索引。
如果不存在主键,将使用第一个唯一 (UNIQUE) 索引作为聚集索引。
如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。
回表查询:举例如下图中执行左侧的SQL语句时,它会先在name字段创建的二级索引中查找Arm的数据对应的主键id,应为返回的是对应id的完整数据,索引会进行回表查询,在聚集索引中查询对应id的完整数据。
避免回表:在设计索引和编写查询语句时,尽量让查询的字段 “被索引覆盖”(即查询字段都在索引中),避免需要回表才能拿到完整数据,从而减少磁盘 IO,提升查询性能。
索引语法
创建索引
CREATE [UNIQUE|FULLTEXT] INDEX 索引名 ON 表名(字段名);
字段名顺序不可以乱写
查看索引
SHOW INDEX FROM 表名;
删除索引
DROP INDEX 索引名 ON 表名;
SQL性能分析
查看执行频次
可以查看当前数据库增删改查的执行频次
show global status like 'Com_ _ _ _ _ _ _'
慢查询日志
慢查询日志记录了所有执行时间超过指定参数(long query time,单位:秒,默认10秒)的所有SQL语句的日志。
查看是否开启慢查询日志
show variables like 'slow_query_log';
慢查询配置文件/etc/my.cnf
开启MySQL慢日志查询开关
slow_query_log=1
设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2
更改配置后要重启mysqld服务
在/var/lib/mysql的目录下查看日志
Show profiles
show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。
查询当前mysql是否支持profile操作
SELECT @@have_profiling
开启profile,默认是关闭
set profiling=1
查看每一条SQL的执行耗时情况。
show profiles;
查看指定的语句id的耗时情况
show profile for query 语句id;
查看指定的语句id的cpu消耗情况
show profile cpu for query 语句id;
explain执行计划
在sql语句前加上explain或者desc可以查看sql语句执行计划
--id:select查询的序列号,表示查询中执行select子句或者是操作表的顺序(id相同,执行顺序从上向下;id不同,值越大,越先执行)
--select_type:表示select的类型,参考意义不大。
--type:表示连接类型,性能由好到坏的连接类型为null,system,const,eq_ref,ref,range,index,all。不访问任何表为null;访问系统表为system,根据主键或者唯一索引访问为const,使用非唯一索引为ref,使用了索引但是遍历了整个索引为index;没有用到索引,全表扫描为all。
--possible_key:显示条件的查询可能应用在这张表上的索引,一个或多个。
--key:实际使用的索引,如果为null,就是没有使用索引。
--key_len:表示索引中使用的字节数,该值为索引字段最大可能长度,再不损失精度的前提下,长度越短越好。
--rows:mysql认为必须要执行查询的行数,在InnoDB引擎的表中,是一个估计值,可能不总是准确的。
--filtered:表示返回结果的行数占需读取行数的百分比,filter的值越大越好。 --extra:展示额外的信息。
索引使用规则
---
最左前缀法则
如果索引了多列(联合索引),要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。如果跳跃某一列,索引将部分失效(后面的字段索引失效)。
例如我创建了profession,age,status顺序的联合索引,下图中的条件是按照最左前缀来查询数据,所以用到了联合索引中的所有字段。
下图中因为跳过了age,所以导致status字段无法使用到索引,索引部分失效
如果查询条件只有profession或者只有profession和age,依然可以使用索引
这是因为联合索引在 B + 树中是按照定义的顺序逐层排序的,先按profession
排序,相同profession
再按age
排序,最后按status
排序。
范围查询
联合索引中出现范围查询(<>大于小于号),范围查询的右侧列索引会失效。
例如下图中使用了范围查询,和上述第一幅图比起来可知,导致status字段的索引失效。
在mysql中使用>=或者<=由explain可以看到右侧的索引不会失效,如下图:
因此在使用范围查询时,应优先使用>=或者<=,这个差异的原因在于:
>
和<
会产生一个开放区间,MySQL 无法确定范围终点,因此无法有效利用后续索引>=
和<=
是闭合区间,MySQL 可以更精确地定位数据范围,从而继续使用右侧索引
索引失效
(1)在索引列上进行运算操作时,会使索引失效,如下图:
(2)字符串类型字段使用时,不加引号,索引将失效,如下图:
(3)如果是尾部模糊查询,索引不会失效;如果是头部模糊查询,索引失效,这是因为B+tree的排序默认就是根据数据头来排序,如下图:
(4)用or分开的条件,如果or的两边不是都有索引,那么涉及的索引都不会被用到,因为一边没有索引就会进行全表扫描,既然有一个字段要进行全表扫描,那就可以顺便把有索引的字段也查找出来,如下图中,age有索引,phone无索引,索引全部失效:
(5)如果mysql评估使用索引比全表扫描更慢,则不使用索引,如下图中,phone的大部分数据都大于17799990010,,profession都不为null,所以没必要走索引,直接全表扫描更快:
SQL提示
SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。
use index:建议查询时使用索引
ignore index:查询时忽略索引
force index:强制查询时使用指定索引
覆盖索引&回表查询
尽量使用覆盖索引(查询使用了索引,并且需要返回的列在该索引中可以全部找到),减少select *。
举例如下图:根据extra可知上面sql语句查找使用了索引,所有数据都在索引列中,不需要回表。下面的sql语句查找也是用了索引,但是因为name字段在该索引中不存在,所以要回表到聚集索引中查询name字段。
Extra中:
using index condition:查找使用了索引,但是需要回表查询数据
using where; using index:查找使用了索引,但是需要的数据都在索引中能找到,所以不需要回表查询数据
什么是回表查询
我们结合下面这张图片来看
第一条语句select * from tb_user where id = 2;
,id
是聚集索引,通过聚集索引可以直接获取到整行数据,不需要回表。
第二条语句select id, name from tb_user where name ='Arm';
,辅助索引(name
)中已经包含了id
和name
这两个需要查询的列,所以也不需要回表,直接从辅助索引就能获取到所需数据。
第三条语句select id,name,gender from tb_user where name ='Arm';
需要回表查询。因为辅助索引(name
)里只包含了id
和name
的信息,而查询还需要gender
列的数据。此时,需要先通过辅助索引找到对应的id
(这里id=2
),然后再根据id
去聚集索引(主键索引)中查询gender
列的值,这个过程就是回表。
思考
一张表,有四个字段(id, username, password, status),由于数据量大,需要对以下SQL语句进行优化,该如何进行才是最优方案:
因为id为主键(聚集索引),所以为username和password建立联合索引,这样查询时满足覆盖索引,辅助索引下面指向聚集索引,可直接提取,不用回表查询聚集索引。最终,这个联合索引完美满足 “覆盖索引” 的要求,既实现了高效查询,又避免了索引冗余(不用重复存储 id
),是最优设计。
前缀索引
当字段类型为字符串时,当为该字段建立索引时,会让索引变的很大,查询时,浪费大量的磁盘I/O,影响查询效率。此时可以将字符串的一部分前缀建立索引,这样可以节省空间,从而提高索引效率。
创建前缀索引
create index 索引名 on 表名(字段名(前缀长度),...);
前缀长度:可以根据索引的选择性来决定,而选择性是指不重复的索引值 (基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
我们可以借助以下两个公式查看索引的选择性
select count(distinct email) / count(*) from tb_user ;
select count(distinct substring(email,1,5))/ count(*) from tb_user;
举例如下图:用email字段的前五个作为索引前缀建立索引,当按照email查询数据时,它会截取条件中email的前五个字符Ivbu6,先到email的前缀索引中查找对应的主键id,然后回表到聚集索引中查询到完整的email值,通过对比判断是否等于Ivuu666@163.com,然后在通过前缀索引的链表继续遍历下一个,按照以上操作循环往复,直到链表的下一个节点的前缀值不等于Ivbu6则结束。
也就是说,前缀索引需要回表查询
总结:前缀索引说白了就是“效率换换空间”,舍弃部分查询效率,换取更多存储空间
单列索引和联合索引
单列索引就是一个索引只包含单个列,联合索引就是一个索引包含多个列。
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建立联合索引,而非单列索引。
举例如下图:下图中对phone和name都建立了单列索引,但是在查询时只是用到了phone的单列索引,并不会用到name的单列索引,我个人的理解是因为在使用到phone的单列索引时会进行回表查询,回表查询时就会把name字段查出来,直接比较就可以了,所以用不到name的单列索引。
当我们为phone和name建立了联合索引时,他会先按照phone排序,phone相同时按照name排序,所以当条件中有这两个字段的条件,可以通过该联合索引快速定位到对应的主键id,不需要回表查询,如下图:
索引设计原则
1.针对于数据量较大,且查询比较频繁的表建立索引。
2. 针对于常作为查询条件 (where)、排序 (order by)、分组 (group by) 操作的字段建立索引。
3.尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
4.如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
5.尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
6.要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。
7.如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。
这是我的个人学习笔记,主要用于记录自己对知识点的理解和梳理。由于目前仍在学习探索阶段,内容中难免存在理解偏差或表述疏漏,恳请各位大佬不吝赐教,多提宝贵意见~ 若有不同看法,欢迎理性交流探讨,感谢包容与指正!