文章学习自互联网以及小林Coding,侵删! 下面先进行快速回顾概念,然后再进行学习Mysql的架构
数据库是所有软件体系中最核心的存在,作用:1. 存储数据 2. 管理数据
开源的Mysql直接用500万以下的数据都没有问题之后就需要优化!
MySQL架构图如下:
来自小林大佬画的图,很清楚可以看到分为server层
和存储引擎层
,
Server 层负责建立连接、分析和执行 SQL;
存储引擎层负责数据的存储和提取,默认为InnoDB,默认索引类型是 B+树。
第一步:连接器
MySQL 客户端与服务器间进行 TCP 三次握手建立连接;
服务端校验客户端的用户名和密码,如果用户名或密码不对,则会报错;
如果用户名和密码都对了,会读取该用户的权限,然后后面的权限逻辑判断都基于此时读取到的权限;
注意定期断开长连接避免内存占用,最大连接用户限制默认151,空闲连接超过默认时间连接器自动断开
第二步: 查询缓存
连接成功,接收SQL语句,查看类型若为查询语句就在缓存中查找之前是否有缓存执行过这条语言的数据,如果有就直接返回,没有就继续执行,待执行完之后,查询语句与执行结果以key-value的形式加入缓存;
由于只要有一个表有更新操作,查询缓存就会被清空,对于更新比较频繁的情况下这个功能几乎无用且耗时,所以MySQL8.0开始就移除了server层的查询缓存。注意:Innodb 存储引擎中的 buffer pool并没有移除
第三步: 解析器
解析SQL语句,先进行词法分析
,识别出关键字构建SQL语法树,然后进行语法分析
,判断是否满足SQL语法,如果关键字错误,或者语法错误这里就会报错并返回。
这里只进行词法和语法分析,表或字段是否存在?这里并不判断
第四步:执行器
根据解析器生成的语法树,进行以下三步走执行:
预处理阶段(prepare)–> 优化阶段(optimize )–> 执行阶段(execute)
预处理阶段
检查 SQL 查询语句中的表或者字段是否存在;如果不存在返回报错
将 select * 中的 * 符号,扩展为表上的所有列;
优化阶段
基于查询成本考虑选择SQL语句的执行方案,比如在表里面有多个索引的时候使用什么索引?
最低效的就是全表扫描。优化可能为主键索引等
执行阶段
根据执行计划执行 SQL 查询语句,从存储引擎读取记录,返回给客户端;
这个时候再来回顾一下这个图就很清楚了,小林coding的图解MySQL讲的很详细了
MySQL的数据由存储引擎实现保存在磁盘上的,InnoDB是默认也是常用的存储引擎,用SHOW VARIABLES LIKE 'datadir';
可以查看MySQL数据库文件存储的目录,每创建一个数据库,这里就会多一个以该数据库名的目录,目录下的三个文件,分别为:
以.opt
的后缀文件存储当前数据库的默认字符集和字符校验规则
以.frm
的后缀文件存储对应表的表结构,保存每个表的元数据信息
以.ibd
的后缀文件存储对应表的表数据表,默认每张表存放在一个独占表空间文件
表空间由段(segment)、区(extent)、页(page)、行(row)组成,InnoDB存储引擎的逻辑存储结构大致如下图:
数据库表中的记录都是按行(row)进行存放的,每行记录根据不同的行格式,有不同的存储结构。
InnoDB 的数据是按「页」为单位来读写的,也就是说,当需要读一条记录的时候,并不是将这个行记录从磁盘读出来,而是以页为单位,将其整体读入内存。默认每个页的大小为 16KB
总之,数据库表中的记录按行存储在「数据页」里。
一行数据的存储结构即行格式,行格式有几种,一种通其他皆会,小林这里讲的是Compact 行格式如下:
这段有点迷,先挖个坑在这,先看看一些问题的答案:
1、varchar(n) 中 n 最大取值为多少?
一行记录最大能存储 65535 字节的数据,但是这个是包含「变长字段字节数列表所占用的字节数」和「NULL值列表所占用的字节数」。所以, 我们在算 varchar(n) 中 n 最大值时,需要减去这两个列表所占用的字节数。
如果一张表只有一个 varchar(n) 字段,且允许为 NULL,字符集为 ascii。varchar(n) 中 n 最大取值为 65532。
计算公式:65535 - 变长字段字节数列表所占用的字节数 - NULL值列表所占用的字节数 = 65535 - 2 - 1 = 65532。
如果有多个字段的话,要保证所有字段的长度 + 变长字段字节数列表所占用的字节数 + NULL值列表所占用的字节数 <= 65535。
2、行溢出后,MySQL 是怎么处理的?
如果一个数据页存不了一条记录,InnoDB 存储引擎会自动将溢出的数据存放到「溢出页」中。如下图
3、MySQL 的 NULL 值是怎么存放的?
MySQL 的 Compact 行格式中会用「NULL值列表」来标记值为 NULL 的列,NULL 值并不会存储在行格式中的真实数据部分。
NULL值列表会占用 1 字节空间,当表中所有字段都定义成 NOT NULL,行格式中就不会有 NULL值列表,这样可节省 1 字节的空间。
4、MySQL 怎么知道 varchar(n) 实际占用数据的大小?
MySQL 的 Compact 行格式中会用「变长字段长度列表」存储变长字段实际占用的数据大小。
https://zhuanlan.zhihu.com/p/430429017
除了参考上面知乎内容还看了另一篇文章的,好像是小牛肉的,都是很不错的文章!!!
innoDB 存储引擎由内存池和一些后台线程组成,下图为InnoDB的内存结构和磁盘结构
先来解释下内存池。
首先,我们需要知道,InnoDB 存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可将其视为基于磁盘的数据库系统(Disk-base Database),在这样的系统中,众所周知,由于 CPU 速度与磁盘速度之间的不匹配,通常会使用缓冲池技术来提高数据库的整体性能。
所以这里的内存池也被称为缓冲池(简单理解为缓存就好了)。
具体来说,缓冲池其实就是一块内存区域,在 CPU 与磁盘之间加入内存访问,通过内存的速度来弥补磁盘速度较慢对数据库性能的影响。
拥有了缓冲池后,“读取页” 操作的具体步骤就是这样的:
“修改页” 操作的具体步骤就是这样的:
所谓 ”脏页“ 就发生在修改这个操作中,如果缓冲池中的页已经被修改了,但是还没有刷新到磁盘上,那么我们就称缓冲池中的这页是 ”脏页“,即缓冲池中的页的版本要比磁盘的新。
至此,综上所述,我们可以得出这样的结论:缓冲池的大小直接影响着数据库的整体性能
innordb基于磁盘存储的,其中的记录以页的方式进行管理,缓解 CPU 与磁盘速度之间的矛盾,基于磁盘的数据库系统通常使用缓冲池技术来提高数据库的整体性能。
Checkpoint 的机制来决定哪些数据该从缓冲池移出去(移到磁盘上)
Innodb 存储引擎设计了一个缓冲池(*Buffer Pool*),来提高数据库的读写性能。
Buffer Pool 以页为单位缓冲数据,可以通过 innodb_buffer_pool_size
参数调整缓冲池的大小,默认是 128 M。
Innodb 通过三种链表来管理缓页:
InnoDB 对 LRU 做了一些优化,我们熟悉的 LRU 算法通常是将最近查询的数据放到 LRU 链表的头部,而 InnoDB 做 2 点优化:
innodb_old_blocks_time
阈值(默认为1秒)」**时,才会将页插入到 young 区域,否则还是插入到 old 区域,目的是为了解决批量数据访问,大量热数据淘汰的问题。可以通过调整 innodb_old_blocks_pc
参数,设置 young 区域和 old 区域比例。
在开启了慢 SQL 监控后,如果你发现「偶尔」会出现一些用时稍长的 SQL,这可因为脏页在刷新到磁盘时导致数据库性能抖动。如果在很短的时间出现这种现象,就需要调大 Buffer Pool 空间或 redo log 日志的大小。
索引的本质是什么?是 B+ 树,是一个存在磁盘上的物理文件。那我们在构建辅助索引 username 的这棵 B+ 树的时候,非聚集索引叶子节点的插入不再是顺序的了,也就是说要去离散地访问磁盘页了。正是由于随机读取的存在导致了插入操作性能下降。
InnoDB 存储引擎开创性地设计了 Insert Buffer。对于辅助索引的插入或更新操作,并不是每一次直接插入到索引页(磁盘页)中,而是先判断插入的辅助索引页是否在 Buffer Pool 中。若需要实现插入记录的辅助索引页不在 Buffer Pool 中,那么就将这个操作首先记录到 Insert Buffer 中,然后在适当的情况下将其合并(Merge)到真正的辅助索引中。
个人理解就是插入数据时候,聚集索引一般建立在主键上面,插入聚集索引一般是顺序的,不需要经过磁盘的随机读取。但是要是若有不是顺序的辅助索引,这个时候插入的辅助索引页就需要进行访问磁盘页,就会导致性能变慢。Innodb就弄了个插入缓冲池,先放到这个里面会以一定的频率和情况,将 Insert Buffer 中的操作应用到原数据页,就像你还了一本书,图书管理员不会去立刻归位,而是以一定的频率和具体情况把这些归还在柜台的书一起分类归位,这样就减少了很多跑来跑去的时间。
后台线程的主要作用就是刷新内存池中的数据,保证内存池中缓存的是最近的数据;此外将已修改的数据文件刷新到磁盘文件,同时保证在数据库发生异常的情况下 InnoDB 能恢复到正常运行状态。
主要作用:“将从磁盘读到的页存放在缓冲池中” 以及 “将缓冲池中的数据以一定的频率刷新到磁盘上
当缓冲池中的某一页的数据被修改就会被标记为脏页,脏页的数据会定期刷新到磁盘,如果从缓冲池刷新到磁盘发生宕机,那么这个数据就不能恢复了。为了避免发生数据丢失的问题,MySQL的事务数据库系统普遍采用WAL(Write Ahead Log
,预写日志)策略。当事务提交时,先写重做日志(redo log),再修改页(先修改缓冲池,再刷新到磁盘);当由于发生宕机而导致数据丢失时,通过 redo log 来完成数据的恢复。
每个 InnoDB 存储引擎至少有 1 个重做日志文件组( redo log group),每个文件组下至少有 2 个重做日志文件(redo log file),默认的话是一个 redo log group,其中包含 2 个 redo log file:
ib_logfile0
和ib_logfile1
。一般来说,为了得到更高的可靠性,用户可以设置多个镜像日志组(mirrored log groups),将不同的文件组放在不同的磁盘上,以此提高 redo log 的高可用性。在日志组中每个 redo log file 的大小一致,并以循环写入的方式运行。
- redo log file 不能设置得太大,如果设置得很大,在恢复时可能需要很长的时间
- redo log file 又不能设置得太小了,否则可能导致一个事务的日志需要多次切换重做日志文件
redo log和缓冲池内存有限,如果数据库运行了很久,这个时候发生宕机,恢复代价非常大,所以引入checkpoint来解决问题
所谓 CheckPoint 技术简单来说其实就是在 redo log file 中找到一个位置,将这个位置前的页都刷新到磁盘中去,这个位置就称为 CheckPoint(检查点)。
。。。
MySQL 架构可以分成俩层,一层是 Server 层,它主要做的是 MySQL 功能层面的事情;另一层就是存储引擎,负责存储与提取相关的具体事宜。
redo log 是 InnoDB 引擎特有的日志,而 Server 层也有自己的日志,包括错误日志(error log)、二进制日志(binlog)、慢查询日志(slow query log)、查询日志(log)。
其他三个日志顾明思意都挺好理解的,需要解释的就是 binlog(二进制日志,binary log),它记录了对 MySQL 数据库执行更改的所有操作,但是不包括 SELECT
和 SHOW
这类操作,因为这类操作对数据本身并没有修改。也就是说,binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如 “给 ID=1 这一行的 a 字段加 1”。
可以看出来,binlog 日志只能用于归档,因此 binlog 也被称为归档日志,显然如果 MySQL 只依靠 binlog 等这四种日志是没有 crash-safe 能力的,所以为了弥补这种先天的不足,得益于 MySQL 可插拔的存储引擎架构,InnoDB 开发了另外一套日志系统 — 也就是 redo log 来实现 crash-safe 能力。
这就是为什么有了 bin log 为什么还需要 redo log 的答案。