当前位置: 首页 > 后端技术 > Java

mysql数据查询过多会不会OOM?

时间:2023-04-01 23:52:04 Java

我的主机内存只有100G,现在要全量扫描一个200G的表,DB主机的内存会不会用完?逻辑备份的时候,不就是扫描整个数据库吗?如果这样会把内存吃光,逻辑备份不是已经死了吗?因此,大表的全表扫描看起来应该是没有问题的。为什么?全表扫描对server层的影响假设我们现在要对一个200G的InnoDB表db1.t进行全表扫描。当然,如果你想在客户端保存扫描结果,你会使用这样的命令:mysql-h$host-P$port-u$user-p$pwd-e"select*fromdb1.t">$target_fileInnoDBdata是存储在主键索引上的,所以全表扫描实际上是直接扫描表t的主键索引。由于这条查询语句没有其他判断条件,所以找到的每一行都可以直接放入结果集中,然后返回给客户端。那么,这个“结果集”存在哪里呢?服务器不需要保存完整的结果集。取数据和发送数据的过程如下:取一行写入“net\_buffer”。这块内存的大小由参数“net\_buffer\_length”定义。默认情况下,重复获取16k行,直到“net\_buffer”已满。如果发送成功,调用网络接口清除“net\_buffer”,然后继续取下一行,写入“net\_buffer”。如果发送函数返回“EAGAIN”或“WSAEWOULDBLOCK”,说明本地网络栈(socket发送缓冲区)已满,进入等待。继续发送查询结果,直到网络栈再次可写。发送过程可以看出:在一个query的发送过程中,MySQL占用的内存最大为“net\_buffer\_length”,不会达到200Gsocket发送缓冲区,也不可能达到200G(默认定义/proc/sys/net/core/wmem\_default),如果socket发送缓冲区满了,读取数据的过程就会被挂起,所以MySQL实际上是在“边读边发送”。这意味着如果客户端接收速度慢,MySQL服务器将无法发送结果,这个事务的执行时间就会变长。例如下面的状态是客户端没有读取“socketreceivebuffer”的内容时在服务器上的showprocesslist看到的结果。如果服务器发送阻塞,如果看到State一直是“Sendingtoclient”,说明服务器上的网络栈满了。如果客户端使用了–quick参数,则会使用mysql\_use\_result方式:读取一行,处理一行。假设某项业务的逻辑比较复杂,如果读取每行数据后要处理的逻辑很慢,那么客户端获取下一行数据的时间会很长,可能会出现上面的结果出现。因此,对于正常的在线业务,如果查询返回的结果很少,建议使用“mysql\_store\_result”接口,将查询结果直接保存到本地内存。当然,前提是查询返回的结果不多。如果太多,客户端会因为执行大查询占用内存近20G。在这种情况下,您需要改用“mysql\_use\_result”接口。如果看到自己负责维护的MySQL中有很多线程在“Sendingtoclient”,说明需要请业务开发同学对查询结果进行优化,评估这么多返回结果是否合理。如果想快速减少该状态下的线程数,可以将“net\_buffer\_length”设置大一些。有时,很多查询语句在实例上的状态是“正在发送数据”,但是检查网络却没有问题。为什么发送数据需要这么长时间?一条查询语句的状态变化如下:MySQL查询语句进入执行阶段后,首先将状态设置为“发送数据”,然后将执行结果的列相关信息(元数据)发送给客户端,继续语句执行过程执行完成后,将状态设置为空字符串。即“Sendingdata”并不一定是“发送数据”,而是可以在执行者进程的任何阶段。比如你可以构造一个锁等待的场景,可以看到Sendingdata的状态。读全表被锁:session1session2beginselect*fromtwhereid=1forupdatestartsatransactionselect*fromtlockinsharemode(blocked)发送数据状态显示session2正在等待锁,状态显示为发送数据。只有当线程处于“等待客户端接收结果”状态时,才会显示“正在发送给客户端”。如果显示为“正在发送数据”,则表示“正在执行”。所以,查询结果是分段发送给客户端的,所以扫描全表,查询返回大量数据,是不会爆内存的。以上就是server层的处理逻辑,在InnoDB引擎中是如何处理的呢?全表扫描对InnoDB的影响InnoDB内存的作用之一就是保存更新的结果,配合redolog避免随机写入磁盘。内存的数据页在BufferPool(简称BP)中进行管理,BP在WAL中起到加速更新的作用。BP还可以加速查询。由于WAL,事务提交时,磁盘上的数据页是旧的。如果此时有读取数据页的查询,redolog是不是应该马上应用到数据页上呢?不必要。因为此时内存数据页的结果是最新的,直接读取内存页即可。此时查询不需要读磁盘,直接从内存中取结果,速度非常快。因此,BufferPool可以加快查询速度。BP对查询的加速效果取决于一个重要指标,即:内存命中率。showengineinnodbstatus的结果可以查看系统当前的BP命中率。一般来说,对于一个服务稳定的在线系统,响应时间要满足要求,内存命中率必须在99%以上。执行showengineinnodbstatus,可以看到“Bufferpoolhitrate”字样,里面显示的是当前的命中率。比如下图中的命中率是100%。如果查询需要的所有数据页都可以直接从内存中获取,那是最好的,对应的命中率是100%。InnoDBBufferPool的大小由参数“innodb\_buffer\_pool\_size”决定。一般建议设置为可用物理内存的60%~80%。大约十年前,单机的数据量是几百G,而物理内存是几G;现在虽然很多服务器可以有128G甚至更高的内存,但是单机的数据量已经达到了T级别。因此,“innodb\_buffer\_pool\_size”小于磁盘数据量是很常见的。如果BufferPool已满,需要从磁盘读取数据页,则必须淘汰旧数据页。InnoDB内存管理使用的最近最少使用(LRU)算法消除了最长未使用的数据。基本LRU算法InnoDB管理BP的LRU算法,用一个链表实现:state1,链表的头部是P1,表示P1是最近刚被访问的数据页。这时有一个读请求访问P3,所以变成状态2,P3被移到前面的状态3,说明这次访问的数据页在链表中不存在,所以需要申请一个新的BP中的数据页Px,加入链表头部。但是因为内存已满,无法再申请新的内存。于是清空链表尾部的Pm数据页内存,存入Px的内容,放在链表的头部,最后最长时间未被访问的数据页Pm被淘汰。如果此时要做全表扫描怎么办?扫描一个200G的表,这个表是历史数据表,平时没有业务访问。然后,按照这个算法进行扫描,会将当前BP中的所有数据全部剔除,存入扫描过程中访问到的数据页的内容中。也就是说,这个历史数据表的数据主要存储在BP中。对于一个做商业服务的图书馆来说,这是不行的。你会看到BP内存命中率急剧下降,磁盘压力增大,SQL语句响应变慢。因此,InnoDB不能直接使用原始的LRU。InnoDB对其进行了优化。InnoDB改进的LRU算法将链表按照5:3的比例划分为New区和Old区。图中LRU\_old指向old区的第一个位置,占整个链表的5/8。即靠近链表头部的5/8为New区,靠近链表尾部的3/8为old区。改进后的LRU算法执行流程:状态1,访问P3,由于P3在New区,所以和优化前的LRU一样,移到链表头部=》状态2之后,需要访问一个新的当前链表中不存在的数据页,此时,仍然是在淘汰数据页Pm,但是新插入的数据页Px是放置在“LRU\_old”中的数据页old区,每次访问都要做如下判断:如果数据页在LRU链表中存在时间超过1s,则将其移动到链表头部。如果数据页在LRU链表中存在时间为小于1s,位置保持不变。1s由参数"innodb\_old\_blocks\_time"控制,默认值1000,单位ms,这种策略是为处理全表扫描等操作量身定做的。还是扫描200G的历史数据表:4.扫描过程中,新插入的数据页放在old区。5、一个数据页中有多条记录,这个数据页会被多次访问。顺序扫描,这个数据页第一次访问和最后一次访问的时间间隔不会超过1秒,所以还是会保留在old区6.继续扫描后面的数据,之前的数据页不会了再次被访问,就没有机会移动到链表的头部(Newarea),很快就会被淘汰。可以看出,这种策略最大的好处就是在扫描这张大表的过程中,虽然也使用了BP,但是对youngarea完全没有影响,从而保证了bufferpool在in中的查询命中率响应正常业务。总结MySQL采用了计算和post的逻辑,所以对于数据量大的查询结果,服务器端不会保存完整的结果集。因此,如果客户端没有及时读取结果,会阻塞MySQL的查询过程,但不会炸毁内存。对于InnoDB引擎,由于淘汰策略,大查询不会造成内存爆炸。而且,由于InnoDB改进了LRU算法,冷数据的全表扫描也可以控制对BufferPool的影响。全表扫描还是会消耗IO资源,所以业务高峰期不能直接在主库在线进行全表扫描。