MySQL底层
下图是MySQL底层的一个简单的概括图
# 日志部分
主要就是这部分了,下面简单介绍一下mysql的三种日志
# 三种日志
# bin log
主要记录所有数据库表结构变更(例如CREATE、ALTER TABLE…)以及表数据修改(INSERT、UPDATE、DELETE…)的所有操作。二进制日志(binary log)中记录了对MySQL数据库执行更改的所有操作,并且记录了语句发生时间、执行时长、操作数据等其它额外信息,但是它不记录SELECT、SHOW等那些不修改数据的SQL语句。
主要有下面这几个作用
恢复(recovery):某些数据的恢复需要二进制日志。例如,在一个数据库全备文件恢复后,用户可以通过二进制日志进行point-in-time的恢复。
复制(replication):其原理与恢复类似,通过复制和执行二进制日志使一台远程的MySQL数据库(一般称为slave或者standby)与一台MySQL数据库(一般称为master或者primary)进行实时同步。
审计(audit):用户可以通过二进制日志中的信息来进行审计,判断是否有对数据库进行注入攻击。
除了上面介绍的几个作用外,binlog对于事务存储引擎的崩溃恢复也有非常重要的作用。在开启binlog的情况下,为了保证binlog与redo的一致性,MySQL将采用事务的两阶段提交协议。当MySQL系统发生崩溃时,事务在存储引擎内部的状态可能为prepared和commit两种。对于prepared状态的事务,是进行提交操作还是进行回滚操作,这时需要参考binlog:如果事务在binlog中存在,那么将其提交;如果不在binlog中存在,那么将其回滚,这样就保证了数据在主库和从库之间的一致性。
# redo log
这部分是Innodb数据库的日志,它会记录下每一页的改动,当我们开启事务后,一开始会把日志放入log buffer里,只有当事务提交后才进行持久化存储到本地,可以作为异常宕机或者介质故障后的数据恢复使用
redo log有两个文件默认为48M(可以进行调整,如果太大的话,重启时间会延长),在我们执行完SQL语句后,就会向redo log里面追加日志,因为我们的日志文件是顺序IO(一开始就申请了48M的空间)。然后采用的是append进行追加,所以速度会很快。当两个文件都满了之后,聚会触发检查点(check point),然后把buffer poll里面的脏页数据持久化,然后才会继续追加人日志
三种持久化的方式
- 0.提交事务并不会持久化,而是交给后台线程去做
- 1.提交事务就去持久化(默认)
- 2.提交事务,会写入操作系统缓存去,只要操作系统没挂,数据库挂了也没关系,后续操作系统会定时的写入
# undo log
这个也是innodb 才有的,主要是记录增删插相反的操作,可以用于事务的回滚操作
上面那个是主要的几种,下面这些日志也很重要
错误日志
默认情况下,错误日志是开启的,且无法被禁止。默认情况下,错误日志是存储在数据库的数据文件目录中,名称为hostname.err,其中,hostname为服务器主机名。主要会记录下面这几种错误
- 服务器启动和关闭过程中的信息
- 服务器运行过程中的错误信息
- 事件调度器运行一个事件时产生的信息
- 在从服务器上启动从服务器进程时产生的信息
查询日志
查询日志在MySQL中被称为general log(通用日志),查询日志里的内容不要被"查询日志"误导,认为里面只存储select语句,其实不然,查询日志里面记录了数据库执行的所有命令,不管语句是否正确,都会被记录
在并发操作非常多的场景下,查询信息会非常多,那么如果都记录下来会导致IO非常大,影响MySQL性能,因此如果不是在调试环境下,是不建议开启查询日志功能的。
查询日志的开启有助于帮助我们分析哪些语句执行密集,执行密集的select语句对应的数据是否能够被缓存,同时也可以帮助我们分析问题,所以,我们可以根据自己的实际情况来决定是否开启查询日志。
慢查询日志
慢查询会导致CPU,IOPS,内存消耗过高。当数据库遇到性能瓶颈时,大部分时间都是由于慢查询导致的。 开启慢查询日志,可以让MySQL记录下查询超过指定时间的语句,之后运维人员通过定位分析,能够很好的优化数据库性能。
慢查询日志记录的慢查询不仅仅是执行比较慢的SELECT语句,还有INSERT,DELETE,UPDATE,CALL等DML操作,只要超过了指定时间,都可以称为"慢查询",被记录到慢查询日志中。
默认情况下,慢查询日志是不开启的,只有手动开启了,慢查询才会被记录到慢查询日志中。
事务日志(Redo log)
这个其实就是undo log和redo log了,这个上面有介绍
二进制日志
其实就是bin log,上面有介绍
中继日志
relay log是复制过程中产生的日志,很多方面都跟binary log差不多,区别是: relay log是从库服务器I/O线程将主库服务器的二进制日志读取过来记录到从库服务器本地文件,然后从库的SQL线程会读取relay-log日志的内容并应用到从库服务器上。
玩转MySQL之八-MySQL日志分类及简介 - 知乎 (zhihu.com) (opens new window)
# 页的概念
这个也是Innodb里面的东西,默认情况下buffer poll为128M,然后里面又分为多个页,一页为16KB,格式如下
我们的页其实就是相当于链表(因为有些页可能是空的),当我们有查询过来时,会把数据存储到buffer poll里面。这里我们需要引入3个链表
# 三大链表
# free链表
这个链表有一个基节点,用于记录统计信息,后续是控制块,用于记录空闲页的指针,并不大,当前buffer pool里面有多少个空白页,就有多少个控制块(后续节点)。
当我们从磁盘中取出一页,会先去这个free链表中找第一个控制块节点,然后填充完以后,就会把那个控制块节点删除,然后当我们buffer poll有空白页时,就会把空白页指针添加到free链表中
# flush链表
这个链表就是用于持久化的,因为默认情况下我们修改数据时会先修改buffer pool里面的数据,然后再进行持久化,但是默认情况下,我们可能会有脏页数据,所以我们可以通过这个flush链表,当我们持久化时,只需要持久和flush链表的数据就可以了
# LRU链表
因为我们的buffer pool是有大小限制的,当我们的buffer pool满了之后我们就必须要想办法淘汰某些空间,这个时候我们就用到了lru链表(也叫最近最少使用的链表,我们会对链表里面的页进行淘汰,这个lru链表的逻辑是这样的:当有一页被更新或者是被查询之类的,那个那个页在链表的节点就会移到最前面,只要是有更新操作就会提前,所以满了以后,就会将最后一个节点进行淘汰!因为那是最少使用的)
但是这样还是有问题(当 select * 后可能会把buffer里面的数据全部淘汰掉)所以我们可以把lru链表将链表换成两个区域,一个是占用5/8(热点数据区域),一个占用3/8(冷数据区域),他会进行一个时间设置,如果对一个页的查询间隔时间小于一个设定的时间(假设为一秒),就不会替换掉前面的热点数据区域(因为全表扫描,一个页里面也有多个数据),如果超过就会对前面的热门数据进行替换
# 脏页
当有修改的时候,就会对buffer pool里面的页进行更新,当时它并不会马上持久化到磁盘里面的!如果此时有一个事务去读取时,就会读取到这个数据,所以这是脏页!脏读就是这么来的
当MySQL挂掉了,脏页没有了,会去从磁盘里面读取,而且会结合redo log里面的日志进行数据的整合,返回最新的数据
如何判断是否是脏页?可以通过flush链表来实现
脏页持久化
可以通过redo log,当我们修改完数据后会生成一个redo log,此时不会马上持久化,而是后台会定期通过redo log对我们的数据进行持久化。
为什么不直接将页数据持久化到磁盘中呢?主要是页数据一页是16kb,它是逻辑上是顺序的,而如果直接写入到磁盘里,它存储形式是随机I/O,磁盘访问速度慢
为什么通过redo log进行持久化呢?如果记录到一个文件里面,它是以append()的形式进行追加,不需要去找文件位置,所以是顺序I/O,磁盘访问速度快
# SQL语句执行过程
主要包括service层和存储引擎层
# service层
- 连接器 用于验证用户权限
- 缓存 查询缓存,使用Key/Value的形式,在MySQL8.0已取消,不常使用
- 分析器 分析SQL语句,提取关键字,表格,字段,判断语法是否正确
- 优化器 选择SQL语句的最优执行方案,如:是否走索引,多表查询如何选择关联顺序
- 执行器 执行前会判断用户是否有权限去执行SQL语句,有则用调用引擎的接口,返回接口执行的结果
存储引擎层主要负责从文件中查询数据并返回结果
查询步骤如下: 权限->缓存(8.0删除)->分析器->优化器->执行器->(权限)引擎查询
更新步骤如下: 前面和查询一样,但更新会添加日志(bin log,redo log,详情看日志模块)先记录redo log(prepare)执行完记录bin log 最后在记录redo log(commit)(两段式提交)