MySQL的执行

MySQL的基本架构如图所示:

MySQL 的逻辑架构图

连接器

负责和客户端建立连接、获取权限、维持和管理连接。客户端连接成功后,会取出连接用户的权限信息,该连接在断开之前所有的操作,都依赖该权限信息。
如果客户端长时间空闲,超过参数 wait_timeout 规定的时间,连接器会自动将其断开。

通过 show processlist 查看 Command 列为 sleep 的行,即为该连接是空闲连接。

长连接和短连接

长连接在连接成功后,如果客户端有持续的请求,则一直使用同一个连接。短连接则在执行过几次查询后便断开,下次查询再次建立新的连接。

长连接会随着查询的增多,维护更多的临时资源,可能导致内存过大,被操作系统强行KILL掉,从现象看就是MySQL异常重启了。
可定期断开长连接,或执行 mysql_reset_connection 来重新初始化连接资源(该过程不需要重连和重新验证权限,仅支持5.7或更高版本)

查询缓存

MySQL会将查询语句与结果以键值对的形式缓存起来。如果命中缓存,就可以直接返回结果,效率更高。

但查询缓存失效非常频繁,只要对一个表进行更新,该表上的所有缓存都将失效,对于更新压力大的表来说,查询缓存的命中率非常低。

将参数 query_cache_type 设置成 DEMAND ,按需使用缓存: SELECT SQL_CACHE * FROM T WHERE ID = 1
从8.0开始,不再支持查询缓存。

语法解析与预处理

解析器对SQL语句进行词法分析、语法分析,生成语法树,判断SQL的语法是否合法。预处理器对语法树进一步检查表和字段是否存在,验证是否有执行SQL的权限。

优化器

经过语法解析和预处理,MySQL知道该语句的查询目的。在开始执行语句之前,会对其进行优化,将解析的语法树转换为执行计划。

评估成本

优化器引入一些因子来估算不同执行计划的成本,选出最优的执行计划。
通过 last_query_cost 查询上次SQL执行的成本值:

mysql> SHOW STATUS LIKE 'Last_query_cost';
+-----------------+-------------+
|  Variable_name  |    Value    |
+-----------------+-------------+
| Last_query_cost | 1040.599000 |
+-----------------+-------------+

表示大概要做1040个数据页的随机查找。优化器在评估成本的时候,假设读取任何数据都需要一次磁盘IO,不考虑任何层面的缓存。

优化器失效

有很多原因导致优化器选择错误的执行计划:

  1. 统计信息不准确。

优化策略

执行器

执行器对优化后的查询方案运行。先判断权限,验证通过则打开表,根据表的引擎定义,调用引擎的接口,取出数据返回给用户。

日志系统

重做日志

重做日志(redo log)是InnoDB引擎的日志系统。是物理日志,记录了在某个数据页上做了什么修改。

如果每次更新,都要写入磁盘,造成IO和查询的成本都很高。MySQL采用WAL(Write-Ahead Logging)技术来提升更新操作的效率。其核心思想是先写入日志再更新数据。

当有一条数据要更新的时候,InnoDB引擎会先把记录写在重做日志(redo log)中,并更新内存中的数据(如果数据没在内存中,则将数据缓存在 change buffer 中)。在合适的时候将日志里记录的操作更新到硬盘。

redo log 是大小固定的,从头开始写,写到末尾后又回到开头循环写。

有了redo log可以保证数据库异常重启后,已提交内存未写入磁盘的数据不会丢失。称之为 crash-safe

change buffer :

当需要更新一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话,在不影响数据一致性的前提下,InnoDB 会将这些更新操作缓存在 change buffer 中,这样就不需要从磁盘中读入这个数据页了。
在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行 change buffer 中与这个页有关的操作,即对原始数据进行merge。系统会在查询、关闭连接、已经定期进行merge操作。

change buffer 会持久化到硬盘中。

redo log 主要节省的是随机写磁盘的 IO 消耗(转成顺序写),而 change buffer 主要节省的则是随机读磁盘的 IO 消耗。

归档日志

归档日志(binlog)是Server层的日志系统。是逻辑日志,记录SQL语句的原始逻辑。binlog 是可以追加写入的。追加写是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。

日志的两阶段提交

  1. 在更新数据时,引擎将新数据更新在内存中,同时将更新记录在redo log中,此时,日志处于 prepare 状态。
  2. 引擎通知执行器已完成,可以提交事务。
  3. 执行器生成binlog,并写入磁盘。调用引擎的提交事务接口。
  4. 引擎把刚写入的redo log改成 commit 状态。

如果不使用两阶段提交,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。