MySQL基础架构及底层数据结构(详解)

目录

逻辑架构

连接层

服务层

引擎层

存储层

SQL执行流程

查询缓存

解析器

词法解析

语法解析

优化器

执行器

存储引擎

InnoDB

架构

缓冲池

MyISAM

区别

InnoDB数据存储结构

碎片区

表空间

B树

B+树

B+树和B树的区别

逻辑架构

  • 连接层

    • 系统(客户端)访问 MySQL 服务器前,做的第一件事就是建立 TCP 连接。经过三次握手建立连接成功后,MySQL 服务器对 TCP 传输过来的账号密码做身份认证、权限获取。
      • 用户名或密码不对,会收到一个 Access denied for user 错误
      • 客户端程序结束执行用户名密码认证通过,会从权限表查出账号拥有的权限与连接关联,之后的权限判断逻辑,都将依赖于此时读到的权限
    • TCP 连接收到请求后,必须要分配给一个线程专门与这个客户端的交互。所以还会有个线程池,去走后面的流程。每一个连接从线程池中获取线程,省去了创建和销毁线程的开销。
  • 服务层

    • SQL接口
      • 接收用户的 SQL 命令,并且返回用户需要查询的结果。比如 SELECT ... FROM 就是调用 SQLInterfaceMySQL 支持 DML(数据操作语言)、DDL(数据定义语言)、存储过程、视图、触发器、自定义函数等多种 SQL 语言接口
    • Parser: 解析器
      • 在解析器中对 SQL 语句进行语法分析、语法分析。将 SQL 语句分解成数据结构,并将这个结构传递到后续步骤,以后 SQL 语句的传递和处理就是基于这个结构的。如果在分解构成中遇到错误,那么就说明这个 SQL 语句是不合理的。在 SQL 命令传递到解析器的时候会被解析器验证和解析,并为其创建语法树,并根据数据字典丰富查询语法树,会验证该客户端是否具有执行该查询的权限。创建好语法树后,MySQL 还会对 SQl 查询进行语法上的优化,进行查询重写。
      • Optimizer: 查询优化器
        • SQL 语句在语法解析之后、查询之前会使用查询优化器确定 SQL 语句的执行路径,生成一个执行计划。 这个执行计划表明应该使用哪些索引进行查询(全表检索还是使用索引检索),表之间的连接顺序如何,最后会按照执行计划中的步骤调用存储引擎提供的方法来真正的执行查询,并将查询结果返回给用户。
        • Caches & Buffers: 查询缓存组件
          • MySQL 内部维持着一些 Cache 和 Buffer,比如 Query Cache 用来缓存一条 SELECT 语句的执行结果,如果能够在其中找到对应的查询结果,那么就不必再进行查询解析、优化和执行的整个过程了,直接将结果反馈给客户端。
          • 这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key 缓存,权限缓存等 。这个查询缓存可以在不同客户端之间共享。
          • 从 MySQL 5.7.20 开始,不推荐使用查询缓存,并在 MySQL 8.0 中删除。
  • 引擎层

    • 真正的负责了 MySQL 中数据的存储和提取,对物理服务器级别维护的底层数据执行操作,服务器通过 API 与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。
  • 存储层

    • 所有的数据,数据库、表的定义,表的每一行的内容,索引,都是存在文件系统上,以文件的方式存在的,并完成与存储引擎的交互。当然有些存储引擎比如 InnoDB,也支持不使用文件系统直接管理裸设备,但现代文件系统的实现使得这样做没有必要了。在文件系统之下,可以使用本地磁盘,可以使用DAS、NAS、SAN 等各种存储系统。

SQL执行流程

  • 查询缓存

    • Server 如果在查询缓存中发现了这条 SQL 语句,就会直接将结果返回给客户端。如果没有,就进入到解析器阶段。需要说明的是,因为查询缓存往往效率不高,
    • 两个查询请求在任何字符上的不同(例如:空格、注释、大小写),都会导致缓存不会命中。因此 MySQL 的查询缓存命中率不高。所以在 MySQL8.0 之后就抛弃了这个功能。
  • 解析器

    • 词法解析

      • 你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面的字符串分别是什么,代表什么。 MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。
    • 语法解析

      • 根据词法分析的结果,语法分析器(比如:Bison)会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。如果 SQL 语句正确,则会生成一个这样的语法树
  • 优化器

    • SQL 语句在语法解析之后、查询之前会使用查询优化器确定 SQL 语句的执行路径,生成一个执行计划。 这个执行计划表明应该使用哪些索引进行查询(全表检索还是使用索引检索),表之间的连接顺序如何,最后会按照执行计划中的步骤调用存储引擎提供的方法来真正的执行查询,并将查询结果返回给用户。
    • 优化器会分析不同执行顺序产生的性能消耗不同而动态调整执行顺序。需求:查询每个部门年龄高于 20 岁的人数且高于 20 岁人数不能少于 2人,显示人数最多的第一名部门信息下面是经常出现的查询顺序:     
  • 执行器

    • 在执行之前需要判断该用户是否具备权限。如果没有,就会返回权限错误。如果具备权限,就执行 SQL 查询执行引擎并返回结果。在 MySQL8.0 以下的版本,如果设置了查询缓存,这时会将查询结果进行缓存。

存储引擎

  • 为了管理方便,人们把`连接管理`、查询缓存、语法解析、查询优化这些并不涉及真实数据存储的功能划分为 MysQL server 的功能,把真实存取数据的功能划分为存储引擎的功能。所以在 MySQL server 完成了查询优化后,只需按照生成的执行计划调用底层存储引擎提供的 API,获取到数据后返回给客户端就好了。
  • MySQL 中提到了存储引擎的概念。简而言之,存储引擎就是指表的类型。其实存储引擎以前叫做表处理器,后来改名为存储引擎,它的功能就是接收上层传下来的指令,然后对表中的数据进行提取或写入操作。
  • InnoDB

    • MySQL 从 3.23.34a 开始就包含 InnoDB 存储引擎。大于等于 5.5 之后,默认采用 InnoDB 引擎。
    • InnoDB 是 MySQL 的默认事务型引擎,它被设计用来处理大量的短期(short-lived)事务。可以确保事务的完整提交(Commit)和回滚(Rollback)。
    • 除了增加和查询外,还需要更新、删除操作,那么,应优先选择 InnoDB 存储引擎。除非有非常特别的原因需要使用其他的存储引擎,否则应该优先考虑 InnoDB 引擎。
    • InnoDB 存储引擎是以页为单位来管理存储空间的,页的默认大小为16KB。
    • 表名.ibd 存储数据和索引
    • 架构

      • 缓冲池
        • 通过缓冲池,那些被频繁使用的数据就能直接在内存中访问,从而加快业务处理。为了高效地进行大量读操作,缓冲池被切分成页,每页可以装入多行记录;为了高效进行缓存管理,缓冲池用页的链表实现,通过最近最少使用(LRU)的变种算法,低频使用的数据将会从缓存中淘汰。
        • 通过LRU的变种算法,缓冲池被当作一个列表管理。当需要向池中添加新页时,最近最少被使用的页面将被移出,从而腾出空间,新页则加入到列表的中间位置。
        • 我们进行的增删改查操作其实本质上都是在访问页(包括读、写、创建新页等操作)。而磁盘 I/O 需要消耗的时间很多,而在内存中进行操作,效率则会高很多。
        • 为了能让数据表或者索引中的数据随时被我们所用,DB会申请占用内存来作为数据缓冲池,在真正访问页之前,需要把在磁盘上的页缓存到内存中的 Buffer Pool 之后才可以访问。这样做的好处是可以让磁盘活动最小化,从而减少与磁盘直接进行 I/O 的时间。要知道,这种策略对提升SQL 语句的查询性能来说至关重要。如果索引的数据在缓冲池里,那么访问的成本就会降低很多。
        • 理想情况下,你将缓冲池的大小设置为尽可能大的值,一般buffer pool的大小为物理内存的70%-80%(默认大小 128M)。缓冲池越大,InnoDB内存数据库的行为越多,从磁盘读取数据一次,然后在后续读取期间从内存访问数据。
        • 读写请求
          • 对于读请求,缓冲池能够减少磁盘IO,提升性能。
          • 写请求需要分情况
            • 假如要修改页号为4的索引页,而这个页正好在缓冲池内
              • 直接修改缓冲池中的页,一次内存操作
              • 写入redo log,一次磁盘顺序写操作
              • 这样的效率是最高的。像写日志这种顺序写,每秒几万次没问题。
              • 不会出现一致性问题
                • 读取,会命中缓冲池的页;
                • 缓冲池LRU数据淘汰,会将“脏页”刷回磁盘;
                  • 数据库异常奔溃,能够从redo log中恢复数据;
            • 假如要修改页号为40的索引页,而这个页正好不在缓冲池内
              • 写入redo log,一次磁盘顺序写操作
              • 修改缓冲池中的页,一次内存操作
              • 先把需要为40的索引页,从磁盘加载到缓冲池,一次磁盘随机读操作
              • 写缓冲
                • 它是一种应用在非唯一普通索引页不在缓冲池中,对页进行了写操作,并不会立刻将磁盘页加载到缓冲池,而仅仅记录缓冲变更(buffer changes),等未来数据被读取时,再将数据合并(merge)恢复到缓冲池中的技术。写缓冲的目的是降低写操作的磁盘IO,提升数据库性能。
                • 一致性问题(不会)
                  • 数据库异常崩溃,能够从redo log中恢复数据
                  • 写缓冲不只是一个内存结构,它也会被定期刷盘到写缓冲系统表空间
                  • 数据读取时,有另外的流程,将数据合并到缓冲池
                • 适用场景
                  • 数据库大部分是非唯一索引
                  • 业务是写多读少,或者不是写后立刻读取;
                • 不适用场景
                  • 数据库都是唯一索引
                  • 写入一个数据后,会立刻读取它
    • 更改缓存:更改缓存是一个特殊的数据结构,当受影响的索引页不在缓存中时,更改缓存会缓存辅助索引页的更改。索引页被其他读取操作时会加载到缓存池,缓存的更改内容就会被合并。不同于集群索引,辅助索引并非独一无二的。当系统大部分闲置时,清除操作会定期运行,将更新的索引页刷入磁盘。更新缓存合并期间,可能会大大降低查询的性能。在内存中,更新缓存占用一部分 InnoDB 缓冲池。在磁盘中,更新缓存是系统表空间的一部分。更新缓存的数据类型由 innodb_change_buffering配置项管理。
    • 自适应哈希索引:自适应哈希索引将负载和足够的内存结合起来,使得 InnoDB 像内存数据库一样运行,不需要降低事务上的性能或可靠性。这个特性通过 innodb_adaptive_hash_index 选项配置,或者通过-skip-innodb_adaptive_hash_index 命令行在服务启动时关闭。
    • 重做日志缓存:重做日志缓存存放要放入重做日志的数据。重做日志缓存大小通过innodb_log_buffer_size 配置项配置。重做日志缓存会定期地将日志文件刷入磁盘。大型的重做日志缓存使得大型事务能够正常运行而不需要写入磁盘。
    • 系统表空间:系统表空间包括 InnoDB 数据字典、双写缓存、更新缓存和撤销日志,同时也包括表和索引数据。多表共享,系统表空间被视为共享表空间。
    • 双写缓存:双写缓存位于系统表空间中,用于写入从缓存池刷新的数据页。只有在刷新并写入双写缓存后,InnoDB 才会将数据页写入合适的位置。
    • 撤销日志:撤销日志是一系列与事务相关的撤销记录的集合,包含如何撤销事务近的更改。如果其他事务要查询原始数据,可以从撤销日志记录中追溯未更改的数据。撤销日志存在于撤销日志片段中,这些片段包含于回滚片段中。
    • 每个表一个文件的表空间:每个表一个文件的表空间是指每个单独的表空间创建在自身的数据文件中,而不是系统表空间中。这个功能通过 innodb_file_per_table 配置项开启。每个表空间由一个单独的.ibd数据文件代表,该文件默认被创建在数据库目录中。
    • 通用表空间:使用 CREATE TABLESPACE 语法创建共享的 InnoDB 表空间。通用表空间可以创建在MySQL 数据目录之外能够管理多个表并支持所有行格式的表。
    • 撤销表空间:撤销表空间由一个或多个包含撤销日志的文件组成。撤销表空间的数量由innodb_undo_tablespaces 配置项配置。
    • 临时表空间:用户创建的临时表空间和基于磁盘的内部临时表都创建于临时表空间。innodb_temp_data_file_path 配置项定义了相关的路径、名称、大小和属性。如果该值为空,默认会在innodb_data_home_dir 变量指定的目录下创建一个自动扩展的数据文件。
    • 重做日志:重做日志是基于磁盘的数据结构,在崩溃恢复期间使用,用来纠正数据。正常操作期间,重做日志会将请求数据进行编码,这些请求会改变 InnoDB 表数据。遇到意外崩溃后,未完成的更改会自动在初始化期间重新进行
  • MyISAM

    • MyISAM 提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但 MyISAM 不支持事务、行级锁、外键,有一个毫无疑问的缺陷就是崩溃后无法安全恢复。
    • 5.5 之前默认的存储引擎。优势是访问的速度快,对事务完整性没有要求或者以 SELECT、INSERT 为主的应用针对数据统计有额外的常数存储。故而 count(*) 的查询效率很高
    • 表名.frm 存储表结构
    • 表名.MYD 存储数据 (MYData)
    • 表名.MYI 存储索引 (MYIndex)
  • 区别

    • InnoDB 支持外键、事务,MyISAM 不支持
    • InnoDB 支持行级锁,MyISAM 表锁
    • InnoDB 写效率较差,MyISAM 读速度快。
    • InnoDB数据与索引一起保存.ibd,MyISAM表结构.frm 索引.myi 数据.myd
    • InnoDB 崩溃后可以安全恢复(基于事务),MyISAM 不支持
    • MyISAM 针对数据有额外的常数存储,count(*) 查询效率很高

InnoDB数据存储结构

    • InnoDB将数据划分为若干个页,InnoDB中页的大小默认为16KB。
    • 以页作为磁盘和内存之间交互的基本单位,也就是一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。也就是说,在数据库中,不论读一行,还是读多行,都是将这些行所在的页进行加载。也就是说,数据库管理存储空间的基本单位是页(Page),数据库IO操作的最小单位是页。一个页中可以存储多个行记录。
    • 页可以不在物理结构上相连,只要通过双向链表相关联即可。每个数据页中的记录会按照主键值从小到大的顺序组成一个单向链表,每个数据页都会为存储在它里边的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。
    • 数据页的16KB大小的存储空间被划分为七个部分
      • 文件头(File Header)
      • 页头(Page Header)
      • 最大最小记录(Infimum+supremum)
      • 用户记录(User Records)
      • 空闲空间(Free Space)
      • 页目录(PageDirectory)
      • 文件尾(File Tailer)。
    • 比页大一级的存储结构,在InnoDB存储引擎中,一个区会分配64个连续的页。因为InnoDB中的页大小默认是16KB,所以一个区的大小是64*16KB= 1MB。
    • B+树的每一层中的页都会形成一个双向链表,如果是以页为单位来分配存储空间的话,双向链表相邻的两个页之间的物理位置可能离得非常远,造成随机I/0。
    • 在表中数据量大的时候,为某个索引分配空间的时候就不再按照页为单位分配了,而是按照区为单位分配,甚至在表中的数据特别多的时候,可以一次性分配多个连续的区。虽然可能造成一点点空间的浪费(数据不足以填充满整个区),但是从性能角度看,可以消除很多的随机I/o,功大于过!
    • 由一个或多个区组成,区在文件系统是一个连续分配的空间(在InnoDB中是连续的64个页),不过在段中不要求区与区之间是相邻的。段是数据库中的分配单位,不同类型的数据库对象以不同的段形式存在。当我们创建数据表、索引的时候,就会相应创建对应的段,比如创建一张表时会创建一个表段,创建一个索引时会创建一个索引段。
    • 对于范围查询,其实是对B+树叶了节点中的记录进行顺序扫描,而如果不区分叶子节点和非叶子节点,统统把节点代表的页面放到申请到的区中的话,进行范围扫描的效果就大打折扣了。所以InnoDB对B+树的叶子节点和非叶子节点进行了区别对待,也就是说叶子节点有自己独有的区,非叶子节点也有自己独有的区。存放叶子节点的区的集合就算是一个段( segment),存放非叶子节点的区的集合也算是一个段。也就是说一个索引会生成2个段,一个叶子节点段,一个非叶子节点段。
    • 除了索引的叶子节点段和非叶子节点段之外,InnoDB中还有为存储一些特殊的数据而定义的段,比如回滚段。所以,常见的段有数据段、索引段、回滚段。数据段即为B+树的叶子节点,索引段即为B+树的非叶子节点。
  • 碎片区

    • 默认情况下,一个使用InnoDB存储引擎的表只有一个聚簇索引,一个索引会生成2个段,而段是以区为单位申请存储空间的,一个区默认占用1M (64*16Kb =1024Kb)存储空间,所以默认情况下一个只存了几条记录的小表也需要2M的存储空间么?以后每次添加一个索引都要多申请2M的存储空间么?这对于存储记录比较少的表简直是天大的浪费。这个问题的症结在于到现在为止我们介绍的区都是非常纯粹的,也就是一个区被整个分配给某一个段,或者说区中的所有页面都是为了存储同一个段的数据而存在的,即使段的数据填不满区中所有的页面,那余下的页面也不能挪作他用。
    • 为了考虑以完整的区为单位分配给某个段对于数据量较小的表太浪费存储空间的这种情况,InnoDB提出了一个碎片(fragment)区的概念。在一个碎片区中,并不是所有的页都是为了存储同一个段的数据而存在的,而是碎片区中的页可以用于不同的目的,比如有些页用于段A,有些页用于段B,有些页甚至哪个段都不属于。碎片区直属于表空间,并不属于任何一个段。
    • 所以此后为某个段分配存储空间的策略是这样的:
      • 在刚开始向表中插入数据的时候,段是从某个碎片区以单个页面为单位来分配存储空间的。当某个段已经占用了32个碎片区页之后,就会申请以完整的区为单位来分配存储空间。
      • 所以现在段不能仅定义为是某些区的集合,更精确的应该是某些零散的页面以及一些完整的区的集合。
  • 表空间

    • 是一个逻辑容器,表空间存储的对象是段,在一个表空间中可以有一个或多个段,但是一个段只能属于一个表空间。数据库由一个或多个表空间组成,表空间从管理上可以划分为系统表空间、用户表空间、撤销表空间、临时表空间等。
    • 独立表空间
      • 独立表空间,即每张表有一个独立的表空间,也就是数据和索引信息都会保存在自己的表空间中。独立的表空间(单表)可以在不同的数据库之间进行迁移。
      • 空间可以回收(DROP TABLE操作可自动回收表空间;其他情况,表空间不能自己回收)。如果对于统计分析或是日志表,删除大量数据后可以通过: alter table TableName engine=innodb;回收不用的空间。对于使用独立表空间的表,不管怎么删除,表空间的碎片不会太严重的影响性能,而且还有机会处理。
      • 由段、区、页组成
    • 系统表空间
      • 系统表空间的结构和独立表空间基本类似,只不过由于整个MysQL进程只有一个系统表空间,在系统表空间中会额外记录一些有关整个系统信息的页面,这部分是独立表空间中没有的。

B树

  • 关键字集合分布在整棵树中,即叶子节点和非叶子节点都存放数据。搜索有可能在非叶子节点结束
  • B 树在插入和删除节点的时候如果导致树不平衡,就通过自动调整节点的位置来保持树的自平衡。
  • 其搜索性能等价于在关键字全集内做一次二分查找。

B+树

  • 在 B+ 树中,所有数据记录节点都是按照键值的大小存放在同一层的叶子节点上,而非叶子结点只存储key的信息,这样可以大大减少每个节点的存储的key的数量,降低B+ 树的高度
  • B+ 树天然具备排序功能:B+ 树所有的叶子节点数据构成了一个有序链表,在查询大小区间的数据时候更方便,数据紧密性很高,缓存的命中率也会比B树高。

B+树为什么节点使用双向链表关联,不用单向链表

  • B+树中每个节点之间使用双向链表关联的主要目的是为了更高效地支持范围查询和范围删除操作。双向链表允许从当前节点向前和向后遍历,这对于范围查询是非常有帮助的,因为你可以从一个节点开始,沿着链表移动,同时访问相邻的节点。
  • 如果使用单向链表,你只能从一个方向遍历,这可能会导致在范围查询时需要更多的操作来找到相邻节点,从而影响查询的性能。
  • 综上所述,尽管在某些情况下单向链表也可以工作,但双向链表更适合B+树的数据结构,因为它能够更有效地支持范围查询和范围删除等操作。

B+树和B树的区别

  • 都是使用二分查找法进行查询
  • B树内部节点是保存数据的,而B+树内部节点是不保存数据的,只作索引作用,它的叶子节点才保存数据。
  • B+树相邻的叶子节点之间是通过链表指针连起来的,B树却不是。
  • B+树查询效率更稳定。因为 B+树每次只有访问到叶子节点才能找到对应的数据,而在 B 树中,非叶子节点也会存储数据.这样就会造成查询效率不稳定的情况,有时候访问到了非叶子节点就可以找到关键字,而有时需要访问到叶子节点才能找到关键字。
  • B+树的查询效率更高。这是因为通常 B+树比 B 树更矮胖(阶数更大,深度更低),查询所需要的磁盘 I/O 也会更少。同样的磁盘页大小,B+树可以存储更多的节点关键字。
  • 在查询范围上,B+树的效率也比 B 树高。这是因为所有关键字都出现在 B+树的叶子节点中,叶子节点之间会有指针,数据又是速增的﹐这使得我们范围查找可以通过指针连接查找。而在 B 树中则需要通过遍历才能完成查询范围的查找,效率要低很多。
  • 31
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值