MySQL数据库进阶篇一(存储引擎、索引)

发布于:2024-04-24 ⋅ 阅读:(23) ⋅ 点赞:(0)

一、存储引擎

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';
        在这里插入图片描述
    • 尝试使用慢查询日志:

      • 一个进程执行 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,查看索引选择性的值,在索引选择性和前缀长度 做权衡
    • 前缀索引的查询流程:

      • 查询字符串的前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值时,它可以更好地确定哪个索引最有效地用于查询。