基础架构

mysql-begin-1

连接器

登录时验证用户名密码,把当前用户的权限查出来,并把权限放到当前连接中。

如果客户端太长时间没有操作,连接器就会自动断开,这个时间由参数wait_timeout控制,默认是 8 小时。

使用长连接后,mysql 的内存可能涨的特别快,这是因为 mysql 在执行过程中临时使用的内存是管理在连接对象里面的,这些资源会在连接断开的时候才释放。内存占用太大可能被系统强行杀掉,从现象看就是 mysql 异常重启。

可以通过两种方案解决这个问题:

  1. 定期断开长连接。使用一段时间或程序里判断执行过一个占用内存较大的查询后,断开重连
  2. 如果是 5.7 及以上版本,可以通过执行mysql_reset_connection来重新初始化连接资源,这个过程不需要重连或重新验证。

查询缓存

myysql 拿到一个查询请求后,首先到缓存中看看之前是否执行过该语句。之前执行过的语句及结果可能会以 key-value 的形式存储在内存中。

大多数情况不建议使用查询缓存,因为弊大于利。查询缓存的失效非常频繁,只要对一个表进行更新,该表所有的缓存都会被清空。往往刚存起来还没使用呢,就被清了。除非你的业务就是一张静态表,例如系统配置,省市区,数据字典等。

可以通过设置query_cache_type=DEMAND,这样默认都不适用查询缓存。

mysql 8.0 直接将查询缓存整个功能都删除了。

分析器

首先做词法分析你输入的是多个字符串和空格组成的一条 sql 语句,mysql 需要分析里面的字符串代表什么意思。

分析完每个词的意思后,要进行语法分析,根据语法规则,判断词法分析的结果是否符合语法,如果语句不对,则报错。

优化器

优化器就是表里有多个索引的时候,决定使用哪个索引。或者一条语句有多表 join 的时候,决定各个表的连接顺序。

执行器

开始执行的时候,先判断你对当前表有没有查询的权限,如果没有,就会返回权限错误。如果有权限,则执行流程如下

  1. 先用 InnoDB 引擎接口取这张表的第一行,判断 ID 是不是 10,如果不是则跳过,如果是则将这行存到结果集中
  2. 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行
  3. 将缓存的结果集返回给客户端

在慢查询日志中可以看到 rows_examined 的字段,这个值就是执行器调用引擎接口的次数,但不代表引擎内部实际扫描的行数。

日志

更新和查询的流程有一点不同,更新还涉及两个重要的日志模块:redo log(重做日志)和binlog(归档日志)

redo log

如果每次更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程 IO 成本、查找成本都很高。所以可以临时记一下要更新什么,最后统一更新到磁盘中。这就是 WAL 技术(Write-Ahead-Logging),它的关键点就是先写日志,再写磁盘。

具体来说,当有一条记录需要更新的时候,InnoDB 引擎会先把记录写道 redolog 中,并更新内存,这个时候更新就算完成了,并在系统空闲的时候,将这个操作记录更新到磁盘里。

redolog 是固定大小的,比如可以配置一组 4 个文件,每个文件 1GB,总共可以记录 4GB 的操作,从头开始写,写到末尾又要回到开头循环写

mysql-begin-2

write pos 是当前记录的位置,一边写一边后移,写道 3 号文件末尾后就回到 0 号文件开头。checkpoint 是当前要擦除的位置,也是往后推移并循环的,擦除前要把数据更新到磁盘中。如果 write pos 追上了 checkpoint,这个时候不能再执行新的更新了,要停下来先擦掉一些记录,把 checkpoint 推进一下。

有了 redo log,即使数据库发生异常重启,之前提交的记录也不会丢失,这个能力称为 crash-safe

binlog

mysql 整体来看就是两块:Server 层和引擎层。redo log 是 InnoDB 引擎特有的日志,而 server 层也有自己的日志,称为 binlog

为什么会有两份日志呢?因为最开始 mysql 自带的引擎是 MyISAM,它没有像 redo log 这样的东西,binlog 只能用于归档,所以另一家公司开发了 InnoDB 引擎,提供另一套日志系统,也就是 redo log 来实现 crash-safe 的能力

1
update T set c=c+1 where id=2;
  1. 执行器找到 id 为 2 这一行,如果 id=2 这行数据在内存中,就直接返回给执行器;否则先从磁盘读入内存,然后返回
  2. 执行器拿到引擎给的行数据,把这个值加一,得到修改后的数据后,调用引擎接口写入这行新数据
  3. 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 中,此时 redo log 处于 prepare 状态,然后告知执行器,随时可以提交事务
  4. 执行器生成这个操作的 binlog,并把 binlog 写入磁盘
  5. 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改为 commit 状态,更新完成