02.16 Day 28 - 大查询

大家好,我是 Snow Hide,作为《MySQL 实战》这个专栏的学员之一,这是我打卡的第 28 天,也是我第 91 次进行这种操作。

今天我温习了该专栏里一篇叫《我查这么多数据,会不会把数据库内存打爆?》的文章。

关键词总结:全表扫描对 server 层的影响(取数据和发数据的流程、流程细节、边读边发、Sending to client、查询结果不多的情况、Sending data、查询语句状态变化、)、全表扫描对 InnoDB 的影响(内存数据页管理、Buffer Pool 对查询加速所依赖的指标、系统当前 BP 命中率、InnoDB Buffer Pool 大小设置、InnoDB 内存管理所使用的算法、LRU 算法的链表实现、InnoDB 改进之后的 LRU 算法(流程、逻辑))。

所学总结:

全表扫描对 server 层的影响

取数据和发数据的流程

  • 获取一行,写到 net_buffer 中。这块内存的大小是由参数 net_buffer_length 定义的,默认是 16K;
  • 重复获取行,直到 net_buffer 写满,调用网络接口发出去;
  • 如果发送成功,就清空 net_buffer,然后继续取下一行,并写入 net_buffer
  • 如果发送函数返回 EAGAINWSAEWOULDBLOCK,就表示本地网络栈(socket send buffer)写满了,进入等待。直到网络栈重新可写,再继续发送。

流程细节

  • 一个查询在发送过程中,占用的 MySQL 内部的内存最大就是 net_buffer_length 的大小,并不会达到表的大小;
  • socket send buffer 也不可能达到表的大小(默认定义 /proc/sys/net/core/vmem_default),如果 socket send buffer 被写满,就会暂停读数据的流程。

边读边发

如果客户端接收得慢,会导致 MySQL 服务端由于结果发不出去,事务执行时间变长。

Sending to client

等待客户端接收结果,表示服务器端的网络栈写满了。
net_buffer_length 参数设置为一个更大的值是一个可选方案。

查询结果不多的情况

建议使用 mysql_store_result 接口,直接把查询结果保存到本地内存。

Sending data

正在执行,可能是处于执行器过程中的任意阶段。

查询语句状态变化

  • MySQL 查询语句进入执行阶段后,首先把状态设置成 “Sending data”;
  • 然后,发送执行结果的列相关的信息(meta data)给客户端;
  • 再继续执行语句的流程;
  • 执行完成后,把状态设置成空字符串。
     

全表扫描对 InnoDB 的影响

内存数据页管理

内存数据页是在 Buffer Pool(BP)中管理的,在 WAL 里 Buffer Pool 起到了加速更新的作用。而实际上,Buffer Pool 还有一个更重要的作用,就是加速查询。

Buffer Pool 对查询加速所依赖的指标

内存命中率。

系统当前 BP 命中率

  • show engine innodb status 结果中查看系统当前的 BP 命中率;
  • 一个稳定服务的线上系统,要保证响应时间符合要求的话,内存命中率要在 99% 以上;
  • 执行 show engine innodb status 后看到的 “Buffer pool hit rate” 滋养就是当前的命中率。

InnoDB Buffer Pool 大小设置

由参数 innodb_buffer_pool_size 确定的。一般建议设置成可用物理内存的 60%~80%。

InnoDB 内存管理所使用的算法

最近最少使用(LRU,Least Recently Used)算法,淘汰最久未使用的数据。

LRU 算法的链表实现

  • 链表头部是最近刚刚被访问过的数据页;假设内存里只能放下一定量的数据页;
  • 这时有个读请求访问排名第二的数据页,因此其被移到最前;
  • 当访问的数据页不存在于链表中时,需要在 Buffer Pool 中新申请一个数据页并添加到链表头部。但由于内存已经满了,不能申请新的内存。于是,会清空链表末尾数据页的内存,存入新的内容,然后放到链表头部;
  • 效果上是最久没有被访问的数据页被淘汰了。

InnoDB 改进之后的 LRU 算法

流程

  1. 要访问排名 3 的数据页,由于其在 young 区域,因此和优化前的 LRU 算法一样,将其移到链表头部;
  2. 之后要访问一个新的不存在于当前链表的数据页,这时候依然是淘汰掉最后的数据页,但是新插入的数据页,是放在 LRU_old 处;
  3. 处于 old 区域的数据页,每次被访问的时候都要做下面的判断:
    • 若这个数据页在 LRU 链表中存在的时间超过了 1 秒,就把它移动到链表头部;
    • 如果这个数据页在 LRU 链表中存在的时间短于 1 秒,位置保持不变。1 秒这个时间,是由参数 innodb_old_blocks_time 控制的。其默认值是 1000,单位毫秒。

逻辑

  1. 扫描过程中,需要新插入的数据页,都被放到 old 区域;
  2. 一个数据页里面有多条记录,这个数据页会被多次访问到,但由于是顺序扫描,这个数据页第一次被访问和最后一次被访问的时间间隔不会超过 1 秒,因此还是会被保留在 old 区域;
  3. 再继续扫描后续的数据,之前的这个数据页之后也不会再被访问到,于是始终没有机会移到链表头部(也就是 young 区域),很快就会被淘汰出去。
     

末了

重新总结了一下文中提到的内容:MySQL 的查询结果、发送给客户端的过程、边算边发的逻辑、不在 server 端保存完整的结果集、会堵住 MySQL 查询过程但不会打爆内存、InnoDB 引擎内部由于淘汰策略的存在所以大查询不会导致内存暴涨、InnoDB 对 LRU 算法做的改进使冷数据全表扫描对 Buffer Pool 的影响可控、业务高峰期还是不能在主库执行全表扫描。

发布了154 篇原创文章 · 获赞 10 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/stevenchen1989/article/details/104337958
今日推荐