SQL 查询流程

- 连接器建立连接
1 | 1. 客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 wait_timeout 控制的,默认值是 8 小时,如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提醒: Lost connection to MySQL server during query。 |
- 查询缓存
1 | 1. MySQL 拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以 key-value 对的形式,被直接缓存在内存中。key 是查询的语句,value 是查询的结果。如果你的查询能够直接在这个缓存中找到 key,那么这个 value 就会被直接返回给客户端。 |
- 分析器进行词法分析和语法分析
1 | 1. 词法分析:例如 MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语句 |
- 优化器优化SQL
1 | 1. 优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。原则是:尽可能扫描少的数据库行纪录,让查询更快。 |
- 执行器
1 | 1. 开始执行的时候,要先判断一下你对这个表 T 有没有执行查询的权限,如果没有,就会返回没有权限的错误,如下所示 (在工程实现上,如果命中查询缓存,会在查询缓存返回结果的时候,做权限验证。查询也会在优化器之前调用 precheck 验证权限)。注意,这时候是对 "表" 的权限进行判断,而连接器是验证用户身份。 |
SQL 更新流程
1 | mysql> create table T(ID int primary key, c int); |
- redo log
当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log(粉板)里面,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做,这就像打烊以后掌柜做的事。redo log 在哪,他也是在磁盘上,这也是一个写磁盘的过程,但是与更新过程不一样的是,更新过程是在磁盘上随机 IO,费时。 而写 redo log 是在磁盘上顺序IO。效率要高。
redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
redo log 是 InnoDB 引擎所特有的,所以我们如果再使用 InnoDB 引擎创建表时,如果数据库发生异常重启,之前提交的记录都不会丢失。 InnoDB 正因为有了 redo log (重做日志),才有了 crash-safe 的能力(即使 mysql 服务宕机,也不会丢失数据的能力)。
- binlog
MySQL 整体来看,其实就有两块:一块是 Server 层,它主要做的是 MySQL 功能层面的事情;还有一块是引擎层,负责存储相关的具体事宜。上面我们聊到的粉板 redo log 是 InnoDB 引擎特有的日志,而 Server 层也有自己的日志,称为 binlog(归档日志)。
redo log 和 binlog 的三点区别:
- redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用
- redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。Redo log不是记录数据页“更新之后的状态”,而是记录这个页 “做了什么改动”。binlog 有两种模式,statement 格式的话是记sql语句, row 格式会记录行的内容,记两条,更新前和更新后都有。
- redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
- 数据更新流程
1 | 1. 执行语句前要先连接数据库,这是连接器的工作。在一个表上有更新的时候,跟这个表有关的查询缓存会失效,所以这条语句就会把表 T 上所有缓存结果都清空。这也就是我们一般不建议使用查询缓存的原因。分析器会通过词法和语法解析知道这是一条更新语句。优化器决定要使用 ID 这个索引。然后,执行器负责具体执行,找到这一行,然后更新。 |

浅色框表示是在 InnoDB 内部执行的,深色框表示是在执行器中执行。最后三步看上去有点“绕”,将 redo log 的写入拆成了两个步骤:prepare 和 commit,这就是”两阶段提交”。
- 两阶段提交
由于 redo log 和 binlog 是两个独立的逻辑,如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序。那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。
1 | update T set c=c+1 where ID=2; |