目录
- 一、存储引擎
- 1.1、MySQL体系结构:连接层,Server层,引擎层,存储层
- 1.2、存储引擎
- 1.2.1、存储引擎:InnoDB(MySQL 5.5后默认的存储引擎)
- 1.2.2、存储引擎:MyISAM (MySQL早期默认存储引擎)
- 1.2.3、存储引擎:Memory
- 1.2.4、InnoDB, MyISAM, Memory的区别,使用场景
- 二、索引
- 2.1、索引结构:B+Tree, Hash, R-tree
- 2.2、索引分类:主键/唯一/常规/全文索引,聚集/二级索引
- 2.3、索引语法:索引的创建,查看,删除
- 2.4、SQL性能分析:执行频率,慢查询日志,profile,explain
- 2.5、索引使用:单列索引,联合索引,前缀索引
- 2.6、索引使用:验证索引效率,索引失效,SQL提示,覆盖索引
- 2.7、索引设计原则
一、存储引擎
1.1、MySQL体系结构:连接层,Server层,引擎层,存储层
- 连接层:连接层
负责处理客户端与MySQL服务器之间的连接和通信
。它接收客户端的连接请求,并建立与客户端的网络连接。 - Server层:包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
- 引擎层:
负责数据的存储和检索
。架构模式是插件式,服务器通过API和存储引擎进行通信。支持 InnoDB、MyISAM、Memory 等多个存储引擎。 - 存储层:MYSQL的物理存储部分,负责将数据(如: redolog、undolog、数据、索引、二进制日志、错误日志、查询 日志、慢查询日志等)存储在磁盘上。
1.2、存储引擎
存储引擎是存储数据、建立索引、更新/查询数据
等技术的实现方式 。存储引擎是基于表
的,而不是基于库的,所以存储引擎也可被称为表类型。可以在创建表的时指定选择的存储引擎,没有指定将自动选择默认的存储引擎。
- 存储引擎的查看和指定
- 使用show create table 表名; 查看建表语句,可以看到当前表所使用的存储引擎
- 在创建表时,指定存储引擎
- 查询当前数据库支持的存储引擎 show engines;
CREATE TABLE 表名(
字段1 字段1类型 [ COMMENT 字段1注释 ] ,
......
字段n 字段n类型 [COMMENT 字段n注释 ]
) ENGINE = INNODB [ COMMENT 表注释 ] ;
1.2.1、存储引擎:InnoDB(MySQL 5.5后默认的存储引擎)
特点:
- DML操作遵循ACID模型,支持
事务
; 行级锁
,提高并发访问性能;- 支持
外键
FOREIGN KEY约束,保证数据的完整性和正确性;
文件:
- xxx.ibd 存储表结构(frm-早期的 、sdi-新版的)、数据和索引
xxx是表名,innoDB引擎每张表都会对应一个表空间文件
show variables like 'innodb_file_per_table'
; 参数代表是否时一张表对应一个文件。MySQL 8.0 版本以后默认打开
逻辑存储结构:
表空间->段->区->页->行
一区可以有64个连续的页
1.2.2、存储引擎:MyISAM (MySQL早期默认存储引擎)
特点:
- 不支持事务,不支持外键
- 支持表锁,不支持行锁
- 优点:更少的存储空间,支持全文索引,适用于读取频率较高、写入频率较低的应用场景
文件:
- xxx.sdi: 存储表结构信息
- xxx.MYD: 存储数据
- xxx.MYI: 存储索引
1.2.3、存储引擎:Memory
Memory引擎的表数据时存储在内存中
,若受到硬件问题、或断电问题的影响,表中数据消失,一般用于临时缓存使用
特点:
- 内存存放,访问速度较快
- hash索引(默认)
文件:
- xxx.sdi:存储表结构信息
- 数据, 都在内存中
1.2.4、InnoDB, MyISAM, Memory的区别,使用场景
区别:
InnoDB引擎与MyISAM引擎的区别 ?
- InnoDB 支持事务,而MyISAM不支持
- InnoDB 支持行锁和表锁,而MyISAM仅支持表锁, 不支持行锁
- InnoDB 支持外键, 而MyISAM不支持
适合的使用场景:
-
InnoDB:对事务的完整性有高要求,且在并发条件下要求数据的一致性,数据操作包含很多更删操作
-
MyISAM : 以读操作和插入操作为主,有少量更新、删除操作,且对事务完整性、并发性要求不高
-
MEMORY:数据保存在内存中,访问速度快,用于临时表及缓存。缺陷就是 对表的大小有限制,太大的表无法缓存在内存中,且断电后数据消失 无法保障数据的安全性。
MyISAM和MEMORY被MongoDB和Redies等 NoSQL 的DBMS所取代
二、索引
索引(index)是MySQL中高效获取数据
的数据结构(有序)
索引优缺点:
优点:
- 提高数据检索效率,降低数据库的I/O成本
- 通过索引对数据进行排序,降低数据排序的成本,降低CPU消耗
缺点:
- 索引占用了数据库的空间 (磁盘便宜
- 索引提高了查询的效率,降低了更新表(Insert,update,delete)的速度,因为增删改表也需要同时维护索引。 (查询的次数远大于增删改操作的次数
2.1、索引结构:B+Tree, Hash, R-tree
MySQL的索引是在存储引擎层中实现的,不同的存储引擎有不同的索引结构:
- B+Tree索引:最常见的索引类型,大部分引擎都支持 B+ 树索引
- Hash索引:底层数据结构是用哈希表实现的,
只有精确匹配索引列的查询才有效,不支持范围查询
- R-tree(空间索引:是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少
- Full-text(全文 索引):是一种通过建立倒排索引,快速匹配文档的方式。
二叉树
如果顺序插入:
- 顺序插入时,会形成一个单项链表,查询性能大大降低
- 大数据量情况下,层级较深,检索速度慢
红黑树
- 解决二叉树的顺序插入后,树不平衡的问题
- 在大数据量的情况下,层级较深,检索速度较慢
B-Tree 多路平衡查找树:
以一颗最大度数为5的b-tree为例:
-
五个指针: 指针1(key<20),指针2(20<key<30),指针3(30<key<62),指针4(62<key<89),指针5(key>89)
-
四个key:20,30,62,89
树的度数指一个结点的子节点个数
中间元素向上分裂
-
一个结点可以包含2个以上的子节点,解决了红黑树 层级较深的问题
-
B树中,非叶子节点和叶子结点都会存放数据,一页中可以放的指针和数据太少,IO次数变多
B+树:
以一个最大度数为4的b+树为例:
度数为4,key有三个,指针有四个
- 索引部分:仅仅起到索引数据的作用,不存储数据
- 数据存储部分,在其叶子节点中要存储具体的数据
与 B-Tree相比,区别:
所有的数据都会出现在叶子节点。
叶子节点形成一个单向链表。
非叶子节点仅仅起到索引数据作用,具体的数据都是在叶子节点存放的。
MySQL优化后的B+ Tree:
- 在原B+Tree的基础上,变成了双向循环链表,形成了带有顺序指针的B+Tree
为什么 InnoDB 存储引擎选择使用 B+tree 索引结构?
- 相对于二叉树,层级更少,搜索效率高;
- 对于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储 的键值减少,指针跟着减少,要同样保存大量数据,只能增加>树的高度,导致性能降低;
- 相对Hash索引,B+tree支持范围匹配及排序操作;
2.2、索引分类:主键/唯一/常规/全文索引,聚集/二级索引
-
如果存在主键,主键索引就是聚集索引
-
如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。 (在字段上加了唯一约束的时候,会自动加上该字段的唯一索引
-
如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引
以下哪个SQL语句的执行效率会更高,为什么?(id为主键,name字段创建的有索引
①select * from user where id = 10;
②select * from user where name = 'Arm';
答:语句①只需要一次索引扫描,语句②需要先查找主键 再回表使用聚集索引获取一整行数据;因此语句①的执行效率会更高
InnoDB主键索引的B+Tree高度为多高?
答:假设一行数据大小1k,一页大小为16K 可以存储16行这样的数据,InnoDB指针占用6个字节空间空间,假设key占用8个字节,
树高度为2:
非叶子结点页存储key的数量:n * 8 + (n + 1) * 6 = 16 * 1024, n = 1170,key 1170个,指针1171个
一个指针指向一个叶子结点的页,一页能存储16行,所以可以存放1171 * 16 = 18736
树高度为3:
1171 * 1171 * 16 = 273,993,856 可以存放百万级别的数据
2.3、索引语法:索引的创建,查看,删除
- 创建索引
CREATE [UNIQUE | FULLTEXT] INDEX index_name ON table_name (index_col_name, ...) ;
#index_name 索引名;index_col_name, ...多个字段
如果一个索引只关联一个字段,则该索引称为单列索引
如果一个索引关联多个字段,则该索引称为 联合索引/组合索引
-
查看该表的所有索引
SHOW INDEX FROM table_name
; -
删除该表的indext_name的索引
DROP INDEX index_name ON table_name
;
举例
2.4、SQL性能分析:执行频率,慢查询日志,profile,explain
-
SQL 执行频率:使用命令查看全局/当前会话的增删改查次数 (7个下划线)
show [session|global] status like 'Com_______'
; 可以提供服务器增删改查的访问频次
主要对于查询较多的数据库的数据库进行优化,若以增删改为主 考虑不对其进行索引优化
session 当前会话的,golbal 全局的
-
使用慢查询日志定位查询效率较低的SQL语句,从而对单个SQL语句进行优化
-
慢查询日志:记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有 SQL语句的日志
- 查看MySQL的慢查询日志是否开启,默认关闭:
show variables like 'slow_query_log';
- 查看MySQL的慢查询日志是否开启,默认关闭:
-
尝试使用慢查询日志:
- 一个进程执行
sudo tail -f /var/log/mysql/mysql-slow.log
查看日志文件的尾行 - 一个进程进入
MySQL执行 SELECT BENCHMARK(1000000000, 1+1);
进行长时间的压测
- 一个进程执行
-
-
profile查看指令耗时。执行指令时,当查询时间超过设置时间后才会写慢查询入日志,但有些SQL语句任务简单 时间在超过的设置时间左右 是不合理的,如何发现这类SQL语句 可以使用 profile 在做SQL优化时帮助我们了解时间耗费情况
-
查看当前数据库是否支持profile
select @@have_profiling
; -
查看当前数据库是否打开了 profiling
select @@profiling
; -
开启profiling
SET profiling = 1
; -
使用指令查看当前会话指令的执行耗时
show profiles
; 查看每一条SQL的耗时基本情况show profile for query query_id
; 查看指定query_id的SQL语句各个阶段的耗时情况show profile cpu for query query_id
; 查看指定query_id的SQL语句CPU的使用情况
-
-
explain执行计划:其它的都是关于时间上的优化,explain是关于执行顺序的优化。
EXPLAIN 或 DESC命令获取 MySQL 如何执行 SELECT 语句的信息,包括在 SELECT 语句执行过程中表如何连接和连接的顺序。
explain / desc EXPLAIN SELECT 字段列表 FROM 表名 WHERE 条件 ;
直接在select语句之前加上关键字
Explain获取的信息:-
id:select查询的序列号,表示查询中执行select子句或者是操作表的顺序
- id相同,执行顺序从上到下
- id不同,值越大,越先执行。
-
select_type:表示 SELECT 的类型,常见的取值有:
- SIMPLE,简单表,即不使用表连接 或者子查询
- PRIMARY,主查询,即外层的查询
- UNION,UNION 中的第二个或者后面的查询语句
- SUBQUERY,SELECT/WHERE之后包含了子查询)
-
type:表示连接类型,性能由好到差的连接类型为:
NULL、system、const、 eq_ref、ref、range、 index、all
NULL性能最好,all性能最差。
-
possible_key:显示可能应用在这张表上的索引,一个或多个
-
key:实际使用的索引,如果为NULL,则没有使用索引。
-
key_len:表示索引字段最大可能长度,并非实际长度,不损失精确性的前提下越短越好
-
rows:MySQL认为必须要执行查询的行数,innodb引擎的表中是一个估计值, 并不总是准确
-
filtered:表示返回结果的行数占需读取行数的百分比,值越大越好
-
extra:额外字段
-
2.5、索引使用:单列索引,联合索引,前缀索引
- 联合索引:即一个索引包含了多个列
- 最左前缀法则:联合索引的使用要遵循最左法则,最左前缀法则指的是查询从索引的最左列开始, 并且不跳过索引中的列。如果跳跃某一列,索引将会部分失效(后面的字段索引失效)
profession、age、status三个字段都使用到了索引,与查询时的位置顺序无关
# 1. 为表中的字段:profession、age、status创建联合索引 create index pro_age_sta_idx on tb_user(profession,age,status); # ----------- 测试 联合索引是否失效,数字是explain的索引长度---------------- # profession、age、status三个字段都使用到了索引 57 explain select * from tb_user where profession = '软件工程' and age = 31 and status = '0'; # profession、age 字段使用到了索引 49 explain select * from tb_user where profession = '软件工程' and age = 31; # profession 字段使用到了索引 47 explain select * from tb_user where profession = '软件工程'; # 全表扫描,没有使用索引,不符合最左前缀法则 NULL explain select * from tb_user where age = 31 and status = '0'; explain select * from tb_user where status = '0'; # profession 字段使用到了索引,跳过了age,因此age后的status字段无法使用索引 47 explain select * from tb_user where profession = '软件工程' and status = '0'; # profession、age、status三个字段都使用到了索引,与查询时的位置顺序无关 57 explain select * from tb_user where age = 31 and status = '0' and profession = '软件工程';
- 最左前缀法则:联合索引的使用要遵循最左法则,最左前缀法则指的是查询从索引的最左列开始, 并且不跳过索引中的列。如果跳跃某一列,索引将会部分失效(后面的字段索引失效)
- 单列索引:即一个索引只包含单个列
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时建议建立联合索引, 而非单列索引。
- 前缀索引
字段类型为字符串(varchar,text),若需要索引是很长的字符串,会使索引长度过长,浪费大量磁盘IO 影响查询效率。因此仅对字符串的一部分前缀建立索引,节约索引空间,提高查询效率-
创建前缀索引:
create index idx_xxxx on table_name(column(n)) ;
-
前缀长度 n:根据索引选择性决定,选择性指不重复的索引值(基数)和 表记录总数的比值,索引选择性越高则查询效率越高。例如:唯一索引的选择性是1,是最好的索引选择性,性能最好
# 查询使用email整个字符串的索引选择比 1.0000 select count(distinct email) / count(*) from tb_user; # 查询使用email 使用前缀5个字符串的索引选择比 0.9583 select count(distinct substring(email,1,5)) / count(*) from tb_user ; # 查询使用email 使用前缀2个字符串的索引选择比 0.9167 select count(distinct substring(email,1,2)) / count(*) from tb_user ; # 对字段email建立前缀索引,前缀长度为5 create index email_idx on tb_user(email(5)); # 查看使用email前缀索引进行查询的执行结构 explain select * from tb_user where email = 'xiaoyu666@qq.com';
-
前缀索引的查询流程:
- 查询字符串的前N个字符串去索引表中查找 获取到对应前缀的主键ID,找到后回表去主键表中获取到整行数据(整个字符串),对比是否与查找字符串相等
- 若在二级索引的表中 下一条数据的前缀N和当前查找的前缀N不等,则直接返回;
- 若相等,则获取下一条数据的主键ID,回表去主键表中获取整行数据,查看字符串是否和查询字符串相等;
-
2.6、索引使用:验证索引效率,索引失效,SQL提示,覆盖索引
-
验证索引效率
-
在未建立索引时,执行SQL语句 查看SQL耗时:
select * from tb_sku where sn = '100000003145001';
-
对字段sn建立索引后,重新进行查询,查看SQL耗时
create index sn_idx on tb_sku(sn);
select * from tb_sku where sn = '100000003145001';
show index from tb_sku;
-
使用explain解释和分析 查询语句
explain select * from tb_sku where sn = '100000003145001';
-
索引失效情况:
-
不遵循最左前缀法则(联合索引):没有有从索引的最左列开始,联合索引失效;中间跳过了索引的中间字段,则该字段后的联合索引都失效
-
范围查询(联合索引):联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效
# profession、age使用到了索引, 49 explain select * from tb_user where profession = '软件工程' and age > 30 and status = '0'; # profession、age、status三个字段都使用到了索引 57 explain select * from tb_user where profession = '软件工程' and age >= 30 and status = '0';
-
索引列运算:在索引列上进行运算操作, 索引将失效
-
字符串不加引号:字符串类型字段使用时,不加引号,索引将失效
-
模糊查询:尾部模糊匹配,索引不会失效;头部模糊匹配,索引失效。
-
or连接条件:or左右两侧的字段必须都有索引,若左有 右无 则左的索引失效
-
数据分布影响:如果MySQL评估使用索引比全表更慢,则不使用索引
-
-
SQL提示:是优化数据库的一个重要手段,简单说,是在SQL语句中加入一些人为提示达成优化操作
# SQL提示 使用者根据自己的倾向 建议/忽略/强制使用 MySQL使用哪个索引进行查询 # profession字段有两个索引:单列索引 profession_idx,联合索引 pro_age_sta_idx #use index: 建议MySQL使用哪一个索引完成此次查询(仅仅是建议,mysql内部还会再次进行评估) explain select * from tb_user use index(profession_idx) where profession = '软件工程'; #ignore index: 忽略指定的索引 explain select * from tb_user ignore index(pro_age_sta_idx) where profession = '软件工程'; #force index: 强制使用索引 explain select * from tb_user force index(profession_idx) where profession = '软件工程';
-
覆盖索引:指查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到。尽量使用覆盖索引,减少select *。 尽量返回使用索引就能得到的列,而不是需要回表
2.7、索引设计原则
- 针对于数据量较大,且查询比较频繁的表建立索引
- 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
- 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高
- 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引
- 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间, 避免回表,提高查询效率。
- 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率
- 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含 NULL值时,它可以更好地确定哪个索引最有效地用于查询。