暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MYSQL-NOTE28,全表扫描

kids and edu 2021-08-25
655

对一个 200G 的 InnoDB 表 db1. t全表扫描,结果保存在客户端,命令:

 

mysql -h$host -P$port -u$user -p$pwd -e "select * from db1.t" > $target_file

 

innoDB 的数据是保存在主键索引上的,所以全表扫描实际上是直接扫描表 t 的主键索引。这条查询语句由于没有其他的判断条件,所以查到的每一行都可以直接放到结果集里面,然后返回给客户端。服务端并不需要保存一个完整的结果集。取数据和发数据的流程是这样的:

 

1)获取一行,写到 net_buffer 中。这块内存的大小是由参数 net_buffer_length 定义的,默认是 16k。

2)重复获取行,直到 net_buffer 写满,调用网络接口发出去

3)如果发送成功,就清空 net_buffer,然后继续取下一行,并写入 net_buffer。

4)如果发送函数返回 EAGAIN 或 WSAEWOULDBLOCK,就表示本地网络栈(socket send buffer)写满了,进入等待。直到网络栈重新可写,再继续发送。

 

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

 

如果一个查询的返回结果不多,建议使用 mysql_store_result 这个接口,直接把查询结果保存到本地内存。

 

当一个线程处于“等待客户端接收结果”的状态,显示"Sending to client";

而如果显示成“Sending data”,它的意思只是“正在执行”。

 

查询的结果是分段发给客户端的,因此扫描全表,查询返回大量的数据,并不会把内存打爆。

 

 

全表扫描对 InnoDB 的影响

 

WAL 机制,当事务提交的时候,磁盘上的数据页是旧的,这时候有一个查询要来读这个数据页,,内存数据页的结果是最新的,直接读内存页就可以了。Buffer Pool 还有加速查询的作用。要保证响应时间符合要求的话,内存命中率要在 99% 以上。执行 show engine innodb status ,可以看到“Buffer pool hit rate”字样,显示的就是当前的命中率。

 

如果一个 Buffer Pool 满了,而又要从磁盘读入一个数据页,需要淘汰一个旧数据页。InnoDB 内存管理用的是最近最少使用 (Least Recently Used, LRU) 算法,这个算法的核心就是淘汰最久未使用的数据。

 

我们要扫描一个 200G 的表,而这个表是一个历史数据表,平时没有业务访问它。按照这个算法扫描的话,就会把当前的 Buffer Pool 里的数据全部淘汰掉,也就是说 Buffer Pool 里面主要放的是这个历史数据表的数据。而业务系统需要用的时候Buffer Pool 的内存命中率急剧下降,磁盘压力增加,SQL 语句响应变慢。

 

在 InnoDB 实现上,按照 5:3 的比例把整个 LRU 链表分成了 young 区域和 old 区域。图中 LRU_old 指向的就是 old 区域的第一个位置,是整个链表的 5/8 处。也就是说,靠近链表头部的 5/8 是 young 区域,靠近链表尾部的 3/8 是 old 区域。

 

改进后的 LRU 算法的操作逻辑:

1)扫描过程中,需要新插入的数据页,都被放到 old 区域 ;

2)一个数据页里面有多条记录,这个数据页会被多次访问到,但由于是顺序扫描,这个数据页第一次被访问和最后一次被访问的时间间隔不会超过 1 秒,因此还是会被保留在 old 区域

3)再继续扫描后续的数据,之前的这个数据页之后也不会再被访问到,于是始终没有机会移到链表头部(也就是 young 区域),很快就会被淘汰出去。

 

 


文章转载自kids and edu,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论