序
从这一章开始,将通过一个系列完整的介绍研发人员需要知道的MySQL知识。
先通过整体流程图从全局上了解一条SQL语句在MySQL中的执行过程,建立整体概念,帮助你从高维度理解问题。
大致了解相关知识点即可,通过后续的文章将会逐步讲解各个环节。
范围
本系列文章知识默认基于MySQL 5.7版本InnoDB引擎,若涉及8.0版本将特殊说明。
更新语句的整体流程图
下面依次介绍下每个步骤的作用
Server 层
校验用户名密码
从权限表获取用户拥有的权限并设置
之后这个连接里面的权限判断逻辑,都依赖此时读到的权限,即时用户权限在此期间发生变化,也只有再新建连接的时候才会生效
连接成功后,如果客户端没有后续动作,这个连接就处于空闲状态,show processlist可以看到Comman列为Sleep。
客户端如果长时间不发送Command到Server端,Server端会主动断开连接,超时时间由参数wait_timeout和interactive_timeout控制,默认为8小时。
关于长连接与短连接
长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。短连接则是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个。
由于建立连接过程比较复杂、耗时,建议使用长链接,减少建立连接的次数。
但由于MySQL在执行过程中临时使用的内存是管理在连接对象中,这些资源会在连接断开时才释放。如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),从现象看就是MySQL异常重启了,解决该问题的方案如下:
当为select语句时,之前执行过的语句及结果以key-value对的形式被直接缓存到内存中,以SQL为key,查询结果为value。
大多数情况下,查询缓存都较为鸡肋
因为只要对表执行一次更新,不管更新了多少数据,这个表上所有的查询缓存都会被清空。所以对于更新压力大的数据库来说,查询缓存的命中率会非常低。费劲存起来的缓存可能还没怎么使用,就被一个更新全清空了。
# 查询缓存相关的status变量
mysql>SHOW GLOBAL STATUS LIKE 'QCache\_%';
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Qcache_free_blocks | 1 | --查询缓存中可用内存块的数目。
| Qcache_free_memory | 33268592 | --查询缓存的可用内存量。
| Qcache_hits | 121 | --从QC中获取结果集的次数。
| Qcache_inserts | 91 | --将查询结果集添加到QC的次数,意味着查询已经不在QC中。
| Qcache_lowmem_prunes | 0 | --由于内存不足而从查询缓存中删除的查询数。
| Qcache_not_cached | 0 | --未缓存的查询数目。
| Qcache_queries_in_cache | 106 | --在查询缓存中注册的查询数。
| Qcache_total_blocks | 256 | --查询缓存中的块总数。
# 查询缓存命中率 ≈ Qcache_hits / (Qcache_hits + Qcache_hits + Qcache_not_cached) * 100%
查询缓存QC的大小只有几MB,不适合将缓存设置得太大,由于在更新过程中需要线程锁定QueryCache,因此对于非常大的缓存,可能会看到锁争用问题。
但存在即合理,可以将my.cnf参数query_cache_type设置为2(DEMAND)用户自定义模式,在特殊场景下查询时加上 SQL_CACHE 关键字来使用查询缓存,其他的则默认不使用,以下场景适用查询缓存:
实际业务中大多数时候很难满足上述条件,通常是配置表,数据字典表等静态表才适合,不过此情况也可以考虑通过配置管理系统或者缓存中间件来实现。
# 例如对城市表使用SQL_CACHE关键字来命中查询缓存
select SQL_CACHE * from citys
查询缓存在MySQL 5.6(默认禁用), 5.7(废弃), 8.0(移除)
最后注意:在线上判断SQL执行效率时,最好加上 SQL_NO_CACHE 显示指定不使用查询缓存,才能拿到真实的执行时间。
select SQL_NO_CACHE id,username from userinfo where username='Alice'
到这步就说明要开始真正的执行语句了,因此需要对SQL语句做解析,包括词法分析,语法分析,步骤如下:
这一步会判断表是否存在,列是否存在等问题
优化器主要是计算各种执行方式的成本,确定最终的执行方案,可能包含以下方案一种会多种:
优化器不关心表使用什么存储引擎,但是存储引擎对于优化器是有影响的。优化器会请求存储引擎提供容量或某个具体操作的开销以及表的统计信息。可以通过关键字提示(hint)或 force index来影响优化器的决策过程。
校验表的查询权限
思考:为什么不在优化器或分析器阶段校验权限呢?因为SQL语句要操作的表不只是SQL字面上那些。比如如果有个触发器,得在执行器阶段(过程中)才能确定。之前的阶段是无能为力的。
调用存储引擎接口完成查询
在慢查询日中有一个rows_examined字段,表示这个语句执行过程中扫描了多少行。这个值就是执行器每次调用引擎获取数据行的时候累计的。
有些场景下,执行器调用一次,在引擎内部扫描了多行,因此引擎扫描行数跟 rows_examined 并不是完全相同的。
引擎层
关于记录Redo Log:
这里 Redo Log是先写入Redo Log Buffer。
然后以一定的策略将Buffer中的数据刷到磁盘
InnoDB 的 Redo Log 是固定大小的,比如可以配置为一组 4 个文件,每个文件 的大小是 1GB,从头开始写,写到末尾就 又回到开头循环写。
write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件 开头。checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录 更新到数据文件。
write pos 和 checkpoint 之间的是“粉板”上还空着的部分,可以用来记录新的操作。如 果 write pos 追上 checkpoint,表示“粉板”满了,这时候不能再执行新的更新,得停下 来先擦掉一些记录,把 checkpoint 推进一下。
如果服务宕机,使用Redo Log恢复Buffer Pool,避免丢失数据(关于具体在各种情况下如何恢复数据,由于是面向后端研发人员,此处没有展开说明)
控制Redo Log刷盘策略的参数叫 innodb_flush_log_at_trx_commit:
innodb_flush_log_at_trx_commit = 0
InnoDB 中的Log Thread 没隔1 秒钟会将log buffer中的数据写入到文件,同时还会通知文件系统进行文件同步的flush操作,保证数据确实已经写入到磁盘上面的物理文件。但是,每次事务的结束(commit 或者是rollback)并不会触发Log Thread 将log buffer 中的数据写入文件。
所以,当设置为0 的时候,当MySQL Crash 和OS Crash或者主机断电之后,最极端的情况是丢失1 秒时间的数据变更。
innodb_flush_log_at_trx_commit = 1
这也是InnoDB的默认设置。我们每次事务的结束都会触发Log Thread将log buffer中的数据写入文件并通知文件系统同步文件。
这个设置是最安全的设置,能够保证不论是MySQL Crash 还是OS Crash或者是主机断电都不会丢失任何已经提交的数据。
innodb_flush_log_at_trx_commit = 2
当我们设置为2 的时候,Log Thread 会在我们每次事务结束的时候将数据写入事务日志,但是这里的写入仅仅是调用了文件系统的文件写入操作。而我们的文件系统都是有缓存机制的,所以Log Thread的这个写入并不能保证内容真的已经写入到物理磁盘上面完成持久化的动作。文件系统什么时候会将缓存中的这个数据同步到物理磁盘文件Log Thread 就完全不知道了。
所以,当设置为2 的时候,MySQL Crash 并不会造成数据的丢失,但是OS Crash或者是主机断电后可能丢失的数据量就完全控制在文件系统上了。各种文件系统对于自己缓存的刷新机制各不一样,大家可以自行参阅相关的手册。
建议设置为1,这样MySQL不会丢失数据,损失一点性能,保证数据安全。
Bin Log是MySQL Server层实现的二进制日志,有以下三种格式:
statement(记录会修改数据的原始SQL语句)
row(记录被修改的记录值)
记录行的内容,记两条,更新 前和更新后都有。
mixed(是以上两种level的混合使用)
是以上两种level的混合使用,一般的语句修改使用statment格式保存Bin Log,如一些函数,statement无法完成主从复制的操作,则采用row格式保存Bin Log,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在Statement和Row之间选择一种。新版本的MySQL中队row level模式也被做了优化,并不是所有的修改都会以row level来记录,像遇到表结构变更的时候就会以statement模式来记录。至于update或者delete等修改数据的语句,还是会记录所有行的变更。
Redo Log 是循环写的,空间固定会用完,BinLog 是可以追加写入的。“追加写”是指 Bin Log 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
Bin Log同样有控制刷盘时机的参数 sync_binlog,可配置值说明如下:
sync_binlog = 0
表示当事务提交之后,MySQL不做fsync之类的磁盘同步指令刷新binlog_cache中的信息到磁盘,而让Filesystem自行决定什么时候来做同步,或者cache满了之后才同步到磁盘。
sync_binlog = 1
表示每次事务的 Bin Log 都持久化到磁盘
sync_binlog = n
表示当每进行n次事务提交之后,MySQL将进行一次fsync之类的磁盘同步指令来将binlog_cache中的数据强制写入磁盘。
建议设置为1,保证 MySQL 异常重启之后数据不丢失,损失一点性能,保证数据安全。
注意:Undo Log是逻辑日志,可以理解为:
恢复步骤
注解
指允许数据在两个方向上传输,但是同一时间数据只能在一个方向上传输。
另【全双工】指:允许数据在两个方向上同时传输。
系列文章
下一篇:【MySQL优化(二)】性能监控分析 - Show Profile