重点知识点:
- Mysql基础架构组成,包括了连接器、分析器、查询缓存(8.0版本移除)、优化器、执行器以及最下层的存储引擎。
- 针对InnoDB引擎,介绍更新操作。基于 redolog 和 binlog 的两阶段提交是如何实现 crash-safe的呢?
一条查询语句的执行过程
1. 与mysql建立链接-连接器
连接器负责跟客户端建立连接、获取权限、维持和管理连接。
首先就建立TCP的连接了,然后校验用户名密码,鉴权成功会从权限表中读取出拥有的权限,之后的权限校验都会依赖于它。这意味着,客户端建立连接后,再修改这个客户端的权限是不会生效的。
-- 查询客户端状态,其中Command为Sleep的表示,空闲连接。
-- 空闲时间超过 8小时 将会被断开。可以通过 wait_timeout 设置。
show processlist;
建立连接是相对耗时的,因此尽量用长连接(使用连接池)。但MySQL执行过程中使用的内存是管理在连接对象中的,==大量的长连接累积可能导致内存飙升==。解决方案:1.定时断开长链接,如执行了占用大内存的查询后,程序自动判断断开。2.Mysql5.7版本后,在执行了大操作后,通过执行 mysql_reset_connection
来重新初始化资源,这个过程不会断开连接。
2. 内存中存在结果-查询缓存
如果查询缓存里面存在,那么就直接返回。但是==查询缓存非常容易失效,这导致缓存命中率很低,增加不必要的负担,除非你的业务就是有一张静态表==。例如,在一个表上做任何更新操作,那所有的缓存都会失效。可以通过设置 query_cache_type
关闭查询缓存,MySQL8.0已经移除了这一部分。
3. 检查SQL是否有语法错误-分析器
检查SQL语法是否错误,查询的表和列是否存在等等。这里也会对权限的 precheck 。
4. 尝试优化SQL-优化器
相同结果的SQL,但执行效率可能会天差地别。优化器作用,就是根据各种指标信息,选择一个最优的方案。例如:1.多个索引时,应该选择那个。2.多表join时,先后顺序又是怎样。当然你也可以干预优化过程,有时候会通过force index 强制使用某个索引。
5. 执行查询操作-执行器
分析器的结果告诉了MySQL要做什么,优化器告诉MySQL如何去更好的做。执行器重点负责实施查询过程。首先执行器判断是否具有这个表的查询权限。鉴权完毕,根据表的引擎定义,调用下层的数据引擎定义的接口。数据引擎,根据条件扫描数据,每当发现满足条件的就交给执行器,重复这个过程。最终执行器将结果集返回给客户端。在慢日志中经常会看到一个row_examined字段,它表示,执行器每次调用引擎获取数据行的累加。==row_examined 并不等于 引擎扫描的行数,一般执行器调用一次接口,引擎会扫描多行的==。
一条更新语句的执行过程
下面的执行过程是基于InnoDB数据引擎的,与查询类似,更新也需要连接器,分析器,优化器和执行器,不再重复描述,直奔主题。例如下面的一个更新语句:
update t1 set col1 = 1 where id = 1;
1. 根据 id=1 将数据从磁盘加载到内存中,如果已经在内存中存在则不需要加载。
2. 在内存中 col1 更改为1
3. 调用InnoDB引擎接口,将内存中的行数据,写入到redolog中,数据处于 prepared状态。
因为IO操作成本是非常高的,所以引入了 Write-Ahead logging(简称WAL) 技术,简单说WAL就是,先写入日志中,之后根据一定策略将内存中数据在刷进磁盘当中。至于说redolog 的 prepared状态,是为了通过两阶段提交实现 crash-safe,后面来总结,redo log 、binlog 和两阶段提交。
4. 写入redo log成功后,在写入 server层的 binlog中。
5. 最终在将redolog 状态改变为 commit状态
至此,一条基于InnoDB的更新语句就完成了。下面,详细的介绍一下更新过程中的,redolog binlog 和两阶段提交实现。
redo log 和 bin log 介绍
redo log
- redo log是InnoDB引擎特有的。
- redo log的大小是固定的,循环写,可以配置多个文件。例如配置4个 1G 大小的文件,从第一个文件开始写,当写到第四个文件最后的时候,又会回到第一个文件开头写。有两个指针,一个write pos 指向当前写入的位置,一个check pos,写入时,移动write pos,输入磁盘移动check pos。当 write pos 追上 check pos代表写满了,不能再执行写入操作,必须执行刷盘。
- 引入redo log重要的一点,是为了降低实时刷盘的成本。先写入内存日志,之后根据一定策略将数据刷入到磁盘中,这种机制,又叫做 Write-Ahead log
(WAL)。 - redo log 是有状态的,这一点设计主要是为了实现crash-safe能力。只有commit状态的数据才能被刷入磁盘中。
- redo log是物理日志,相比于逻辑日志。物理日志只会记录在某个数据块的修改,不会记录SQL的逻辑。
bin log
- bin log 是 Mysql Server 层的,不管什么存储引擎,数据最终都会在bin log中记录。
- bin log 是 追加写,写满一个文件之后,则创建另一个接着写。因此借助bin log 可以将数据库恢复到任何一个状态。
- bin log 记录的逻辑日志。
- bin log 的存在重点是为了,数据备份和数据同步。例如主从同步,从库便是监听主库的bin log。又例如一些数据传输中间件,都是利用bin log 将数据从一个地方,传输到另一个地方的。
redo log VS bin log
- redo log 长度固定,循环写,bin log 长度不固定,追加写可以有很多。
- redo log 是InnoDB特有的,bin log是Mysql Server层的,所有引擎公用的。
- redo log 具有prepared 和 commit的状态
- redo log 是一种物理日志,bin log则是逻辑日志。
逻辑日志和物理日志:例如记录SQL语句,它就是逻辑日志,可读性强,具有执行的语法逻辑。物理日志,就比较直接,直接指明在某个磁盘空间的修改,例如将,让xxx地址设置成xxx。记录了最终的结果,至于这个结果是如何产生的,就不可知了。
两阶段提交。
前面提到,redo log 是有状态的。更新过程需要两个阶段才能提交。
- ==第一步==写入 redo log 状态为 prepared
- ==第二步==写入 bin log
- ==第三步==修改 redo log 状态为 commit
这样做主要是为了保证在出现异常时(不包括redo log出现了丢失,关键磁盘损坏),bin log 和 redo log的数据一致,俗称crash safe。
试想,假如没有两阶段提交,先写入 redo log ,再写入 bin log,但在写入bin log之前,Mysql异常了。导致 redo log里面有 bin log中没有,那么对 Mysql 主从来说,主库存在了,从库却没有,这是多么诡异的事情呀。(先写 bin log 再写 redo do,也会存在不一致问题)。
为什么两阶段提交就可以解决redo log 和 bin log得不一致问题实现crash safe呢?
- 第一步成功,第二步之前异常,此时mysql 重启后,读取redo log 发现是 prepared,去看bin log 发现没有,直接回滚掉这条数据。
- 第一步成功,第二步成功,第三部之前失败,此时mysql重启后,读取redo log 发现是 premared状态,但是bin log 中已经有了,则修改为 commit状态并直接提交。
第一步,第二步,第三步的含义请往上瞅。
可能有人会质疑,即使redo log 没有状态,异常重启是,mysql 对比 redo log 和 bin log 差异,然后裁剪彼此差异,也能保持两者一致。但这是有问题,redo log 是一个循环写的结构,如果没有状态,redo log 一旦刷入磁盘,那这部分日志就可以被另外的日志覆盖。这就导致在redo log 没鱼状态的情况下,redo log 和 bin log 差异对比是不严谨,不正确的。