工作中的点点滴滴-mysql缓存衍生的一些知识点

背景:

  上午产品说订单表有一条数据状态,下游没有同步,然后查一下数据行现在是什么状态,然后在更新一下。那我当然是徒手开始select了呀,因为知道id所以直接select where orderNo,顿操作猛如虎,然后执行了一秒多的样子。但是当我在第二次执行同样的sql,结果却一瞬间就返回了,嘿,这就牛了b了呀,这个订单表好歹一两百个w的数据表这么快就有结果了,是不是mysql把我上次查询的结果缓存了啊,然后果不其然,哈哈哈哈。。。

一,mysql的查询语句执行过程是怎么样的呢?

  首先要弄清楚mysql的一共是由那几个部分组成的。知道了这个点,对于了解mysql的执行机制还是有那么点帮助的,

  1.客户端,比如代码中的jdbc,或者桌面app mysqladmin这些都可以称之为mysql客户端,提供sql的最原始的输入的地方。

  2.连接器,关联mysql客户端和服务端连接的,验证权限。

  3.分析器,sql语句的语法分析,词法分析。

  4.缓存组件,缓存中存放了数据页的行号等信息,如果命中了就直接返回,这也就是背景中,为什么有的时候执行sql就非常快的返回给客户端结果了。

  5.优化器,根据sql生产执行计划,选择合适的索引。

  6.执行器,调用存储引擎接口,返回数据结果。

回答最上面的一个疑问,mysql在什么情况下回走查询缓存呢?首先mysql要开启缓存查询,其次两次查询语句的字符串要相等,就是sql语句要一模一样才可以,最后在这两次查询语句过程中,没有对查询的结果数据行有dml操作。

二,上面的通过了解mysql构成大致也明白一条查询语句的执行流程了,那么一条更新操作语句是怎么执行的呢?

  一条更新语语句相对于查询语句来讲,差别主要是体现在执行器这一步,因为在这里更新操作语句还涉及到了两个很重要的日志模块,redo log 和binlog,

  如果让我们自己在一堆数据里面更新一条数据,首先我们是不是要找到那一条数据,然后再去修改。如果是更新的数据行少的时候,你去这样方式去做可能还行,但是当更新的数据多了,你再这样先找到数据,在去更新数据那客户端还不得等得蹦起来呀,所以这个时候我们就要换一个方法了,先把我们要更新的数据记下来,然后在空闲的时候再一股脑的去做更新这个事儿,试想一下,那这样做成本是不是会低一些呢。

  那我们放到mysql来讲,当有一条更新语句来了,InnoDB引擎就会先把记录写到redo log里面,并更新内存,这个时候更新就算完成了。同时,InnoDB引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做,

  所以更新的大致流程理解差不多了,我们来详细说一下在innerDB一条把key=2的value原值1再+1的更新语句是怎么执行:

  1执行器先找到key=2的这行数据,如果key=2的数据页在内存Buffer Pool中,则直接返回,否则查询磁盘,放在内存Buffer Pool中在返回。

  2.执行器拿到这个数据行,把key+1,得到一个新的数据行,调用数据引擎接口写入数据行。

  3.引擎将这个新的数据行更新到内存Buffer Pool,并且老的数据写入到undolog,用于回滚失误,同时把这个更新操作记录到redo log中。此时redo log处于prepare(预备)状态,然后告知执行器执行完成了,随时可以提交事务。

  4.执行器生成这个操作的binlog,并把binlog写入磁盘。

  5.执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log改成提交(commit)状态,更新完成。

  从上面的执行流程可以看出,redolog是分成两次写入的,也就是常说了的"两阶段提交"。那么为什么要这么做呢?试想一下不这么做可以不么?

  1.先写redo log后写binlog。假设在redo log写完,binlog还没有写完的时候,MySQL进程异常重启。由于我们前面说过的,redo log写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行value的值是2。
但是由于binlog没写完就crash了,这时候binlog里面就没有记录这个语句。因此,之后备份日志的时候,存起来的binlog里面就没有这条语句。
然后你会发现,如果需要用这个binlog来恢复临时库的话,由于这个语句的binlog丢失,这个临时库就会少了这一次更新,恢复出来的这一行value的值就是1,与原库的值不同。
  2.先写binlog后写redo log。如果在binlog写完之后crash,由于redo log还没写,崩溃恢复以后这个事务无效,所以这一行value的值是0。但是binlog里面已经记录了“把c从0改成1”这个日志。所以,在之后用binlog来恢复的时候就多了一个事务出来,恢复出来的这一行c的值就是1,与原库的值不同。

  简单说,redo log和binlog都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。

  在上面的几个步骤中,多次提到了一个BufferPool,这个是mysql的一个内存组件,我们所有的增删改操作的数据都是在BufferPool里面,因为如果直接操作磁盘效率会很低下。所以到日志最后commit之后线程会随机的把BufferPool里面的文件异步同步到磁盘。那么BufferPool里面存放的是什么东西呢?主要是这两个,缓存页(从数据页(磁盘上的)加载进来的)和描述数据。一页16kb,数据页里是一行行数据,描述数据包含比如所属的表空间、数据页的编号、这个缓存页在buffer pool中的地址等描述信息。

 

 

 

posted @ 2021-01-26 10:58  小杨ABC  阅读(84)  评论(0编辑  收藏  举报