笔记来源于mysql实战,却高于它
查询语句执行流程
- MySQL可以分为Server层和存储引擎层两部分。
- Server层:所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
- 而存储引擎层负责数据的存储和提取。支持InnoDB、MyISAM、Memory等多个存储引擎。InnoDB从MySQL 5.5.5版本开始成为了默认存储引擎。
- 不同的存储引擎共用一个Server层,也就是从连接器到执行器的部分。你可以先对每个组件的名字有个印象,接下来我会结合开头提到的那条SQL语句,带你走一遍整个执行流程,依次看下每个组件的作用。
连接器:负责跟客户端建立连接、获取权限、维持和管理连接
客户端连接超时由wait_timeout控制的,默认值是8小时。
修改命令
SHOW GLOBAL VARIABLES LIKE 'wait_timeout'; SET GLOBAL wait_timeout=28800;
由该长连接造成内存占用过多解决方案:
-
定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。
-
MySQL 5.7以上版本,通过执行 mysql_reset_connection来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。
查询缓存:以key-value对的形式,被直接缓存在内存中。key是查询的语句,value是查询的结果
多数情况不要使用,表更新,缓存清空然后又存进来。很少更新的静态表和动态表可以使用
静态表:字段有固定长
动态表:字段不定长
用缓存查询,不用缓存就写SQL_NO_CACHE
SELECT SQL_CACHE * FROM 表;
windows的my.ini设置缓存类型. 0 for OFF, 1 for ON and 2 for DEMAND.
query_cache_type=2
查询缓存类型
SELECT @@query_cache_type;
SHOW VARIABLES LIKE '%query_cache%';
SHOW GLOBAL STATUS LIKE '%Qcache%';
- Qcache_free_blocks:表示查询缓存中目前还有多少剩余的blocks,如果该值显示较大,说明查询缓存中的内存碎片过多。
- Qcache_free_memory:表示Query Cache中目前剩余的内存大小。
- Qcache_hits:表示有多少次命中缓存。
- Qcache_inserts:表示多少次未命中缓存然后插入,意思是新来的SQL请求如果在缓存中未找到,不得不执行查询处理,执行查询处理后把结果insert到查询缓存中。
- Qcache_lowmem_prunes:该参数记录有多少条查询因为内存不足而被移出查询缓存。
- Qcache_not_cached: 表示因为query_cache_type的设置而没有被缓存的查询数量。
- Qcache_queries_in_cache:当前缓存中缓存的查询数量。
- Qcache_total_blocks:当前缓存的block数量。
flush query cache; – 整理,不删除缓存数据
reset query cache ; – 可删除缓存内容
备注:从MySQL 8.0版本以后直接取消了查询缓存的整块功能。
分析器:MySQL需要识别出里面的字符串分别是什么,代表什么
它也要把字符串“T”识别成“表名T”,把字符串“ID”识别成“列ID”。
词法分析的结果,语法分析器会根据语法规则,判断你输入的这个SQL语句是否满足MySQL语法
优化器:优化器是在表里面有多个索引的时候,决定使用哪个索引
执行器:调用InnoDB引擎接口取这个表的各行,重复相同的判断,如果不是则跳过,如果是则将这行存在结果集中;
慢查询日志中看到一个rows_examined的字段,表示这个语句执行过程中扫描了多少行。在有些场景下,执行器调用一次,在引擎内部则扫描了多行,因此引擎扫描行数跟rows_examined并不是完全相同的。
核心日志模块
MySQL日志模块其中的三个核心日志,分别是redo log(重做日志)、undo log(回滚日志)、binlog(归档日志)。
crash-safe(字面意思:宕机安全):MySQL 保证数据不会丢的能力主要体现在两方面:
- 能够恢复到任何时间点的状态;-------可以通过重跑binlog实现
- 能够保证MySQL在任何时间段突然奔溃,重启后之前提交的记录都不会丢失;------事务提交过程中任何阶段,MySQL突然奔溃,重启后都能保证事务的完整性,已提交的数据不会丢失,未提交完整的数据会自动进行回滚。这个能力依赖的就是redo log和unod log两个日志。
更新语句的执行流程:
- 执行器先找引擎取ID=2这一行。ID是主键,引擎直接用树搜索找到这一行。如果ID=2这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
- 执行器拿到引擎给的行数据,把这个值加上1,比如原来是N,现在就是N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到redo log里面,此时redo log处于prepare状态。然后告知执行器执行完成了,随时可以提交事务。
- 执行器生成这个操作的binlog,并把binlog写入磁盘。
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log改成提交(commit)状态,更新完成。
WAL(Write Ahead Log):日志先行的技术,指的是对数据文件进行修改前,必须将修改先记录日志。保证了数据一致性和持久性,并且提升语句执行性能。
重做日志 redo log
redo log也称为事务日志,由InnoDB存储引擎层产生。记录的是数据库中每个页的修改,而不是某一行或某几行修改成怎样,可以用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置,因为修改会覆盖之前的)。
MySQL在有事务提交对数据进行更改时,只会在内存中修改对应的数据页和记录redo log日志,完成后即表示事务提交成功,至于磁盘数据文件的更新则由后台线程异步处理。由于redo log的加入,保证了MySQL数据一致性和持久性(即使数据刷盘之前MySQL奔溃了,重启后仍然能通过redo log里的更改记录进行重放,重新刷盘),此外还能提升语句的执行性能(写redo log是顺序写,相比于更新数据文件的随机写,日志的写入开销更小,能显著提升语句的执行性能,提高并发量),由此可见redo log是必不可少的。
redo log是固定大小的,所以只能循环写,从头开始写,写到末尾就又回到开头,相当于一个环形。当日志写满了,就需要对旧的记录进行擦除,但在擦除之前,需要确保这些要被擦除记录对应在内存中的数据页都已经刷到磁盘中了。在redo log满了到擦除旧记录腾出新空间这段期间,是不能再接收新的更新请求,所以有可能会导致MySQL卡顿。(所以针对并发量大的系统,适当设置redo log的文件大小非常重要!!!)
回滚日志 undo log
undo log回滚的作用和多个行版本控制(MVCC),保证事务的原子性。在数据修改的流程中,会记录一条与当前操作相反的逻辑日志到undo log中(可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录),如果因为某些原因导致事务异常失败了,可以借助该undo log进行回滚,保证事务的完整性,所以undo log也必不可少。
归档日志 binlog
binlog在MySQL的server层产生,不属于任何引擎,主要记录用户对数据库操作的SQL语句(除了查询语句)。之所以将binlog称为归档日志,是因为binlog不会像redo log一样擦掉之前的记录循环写,而是一直记录(超过有效期才会被清理),如果超过单日志的最大值(默认1G,可以通过变量 max_binlog_size 设置),则会新起一个文件继续记录。但由于日志可能是基于事务来记录的(如InnoDB表类型),而事务是绝对不可能也不应该跨文件记录的,如果正好binlog日志文件达到了最大值但事务还没有提交则不会切换新的文件记录,而是继续增大日志,所以 max_binlog_size 指定的值和实际的binlog日志大小不一定相等。
正是由于binlog有归档的作用,所以binlog主要用作主从同步和数据库基于时间点的还原。
那么回到刚才的问题,binlog可以简化掉吗?这里需要分场景来看:
如果是主从模式下,binlog是必须的,因为从库的数据同步依赖的就是binlog;
如果是单机模式,并且不考虑数据库基于时间点的还原,binlog就不是必须,因为有redo log就可以保证crash-safe能力了;但如果万一需要回滚到某个时间点的状态,这时候就无能为力,所以建议binlog还是一直开启。
--------待后续补充----------
参考https://mp.weixin.qq.com/s/5i9wmJs4_Er7RaYfNnETyA