百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

MySQL数据查询太多会OOM吗?

csdh11 2025-01-02 15:30 34 浏览

线上 MySQL 直接 Select 千万条的100G数据,服务器会裂开吗?

假设对某100G表t执行全表扫描,把扫描结果保存在客户端:

# 该语句无任何判断条件,所以全表扫描,查到的每行都可直接放到结果集,然后返给客户端
mysql -h$host -P$port -u$user -p$pwd -e 
  "select * from t" > $target_file

那这“结果集”存在哪的?

服务端其实无需保存一个完整结果集,实际上MySQL取、发数据流程如下:

  1. 获取一行,写到net_buffer该内存大小由参数net_buffer_length定义,默认16k
  2. 继续获取行,直到写满net_buffer,然后发出去
  3. 若发送成功,则清空net_buffer,继续读取下一行,并写入net_buffer
  4. 若发送函数返回EAGAINWSAEWOULDBLOCK,表示本地网络栈(socket send buffer)写满,进入等待。直到网络栈重新可写,再继续发送

以上过程执行流程图如下:

可以看出:

  • 一个查询在发送过程中,占用MySQL内部的内存最大就是net_buffer_length,根本达不到100G
  • 同理,socket send buffer 也不会达到100G(默认定义/proc/sys/net/core/wmem_default),若socket send buffer被写满,就会暂停读数据的流程

所以MySQL其实是边读取边发送,若客户端接收得比较慢,会导致MySQL Server由于结果发不出去,该事务的执行时间就会变得很长。如下状态,即当Client不读socket receive buffer内容时,在Server端show processlist的结果 --- 服务端发送阻塞:

Id

User

Host

Db

Command

Time

State

Info

11

root

Localhost:64001

Test

Query

0

Starting

show processlist

12

root

localhost:64003

Test

Query

10

sending to client

select * from citizen

若State一直“Sending to client”,说明Server端网络栈满。若客户端使用–quick参数,会使用mysql_use_result:读一行处理一行。假设某业务较复杂,每读一行数据后要处理的逻辑很慢,就会导致客户端要过很久才能读取下一行数据,可能就会出现上表的结果。

正常的线上业务,若一个查询的返回结果不多,推荐使用mysql_store_result接口,直接把查询结果保存到本地内存。这前提是查询返回结果不多,若太多,因执行一个大查询,导致客户端占用内存近20G,这时就需改用mysql_use_result接口。

若你看到MySQL有很多线程处于“Sending to client”,就得优化业务的查询结果了。若想快速减少处于该状态下的线程,可将net_buffer_length设大些。

有时会看到很多查询语句的state是“Sending data”,这又是为啥?我们得知晓一个查询语句的状态变化:

  1. MySQL查询语句进入执行阶段后,先把状态置为 Sending data
  2. 然后,发送执行结果的列相关的信息(meta data) 给客户端(Sending data)
  3. 再继续执行语句的流程(Sending data)
  4. 执行完成后,把状态置成空串(“”)

所以“Sending data”并不一定指“正在发送数据”,可能是处于执行器过程中的任意阶段。可构造锁等待场景---读全表被锁:

session1

session2

begin select * from citizen where id=1 for update;



select * from citizen lock in share mode; (blocked)

执行show processlist:

Id

User

Host

Db

Command

Time

State

Info

11

root

Localhost:64001

Test

Sleep

59

Starting

Null

12

root

localhost:64003

Test

Query

3

Sending data

select * from citizen lock in share mode

13

root

Localhost:64004

Test

Query

0

Starting

show processlist

可见session2在等锁,state为Sending data,即

  • 仅当一个线程处“等待客户端接收结果”的状态,才会显示"Sending to client"
  • 若显示成“Sending data”,只是表明“正在执行”

所以,查询结果是分段发给客户端的,因此扫描全表,即使查询返回大量数据,也不会把内存搞满。

以上都是server层处理逻辑,InnoDB引擎层又是如何处理的呢?

InnoDB如何处理全表扫描?

InnoDB内存的一个作用,是保存更新的结果,再配合redo log,避免随机写盘。

内存的数据页是在Buffer Pool (简称为BP)管理,在WAL里BP起加速更新的作用。 BP还能加速查询。

  • 由于WAL,当事务提交时,磁盘上的数据页是旧的,若这时马上有个查询来读该数据页,是不是要马上把redo log应用到数据页? 不需要。因为此时,内存数据页的结果是最新的,直接读内存页即可。这时查询无需读磁盘,直接从内存取结果,速度很快。所以,Buffer Pool能加速查询。

而BP对查询的加速效果,依赖于一个重要的指标,即:内存命中率。 可以在show engine innodb status结果中,查看一个系统当前的BP命中率。一般情况下,一个稳定服务的线上系统,要保证响应时间符合要求的话,内存命中率要在99%以上。

执行show engine innodb status ,可以看到“Buffer pool hit rate”字样,显示的就是当前的命中率。比如下图命中率,就是100%。

若所有查询需要的数据页都能够直接从内存得到,那是最好的,对应命中率100%。

InnoDB Buffer Pool的大小是由参数 innodb_buffer_pool_size确定,一般建议设置成可用物理内存的60%~80%。

在大约十年前,单机的数据量是上百个G,而物理内存是几个G;现在虽然很多服务器都能有128G甚至更高的内存,但是单机的数据量却达到了T级别。

所以,innodb_buffer_pool_size小于磁盘数据量很常见。若一个 Buffer Pool满了,而又要从磁盘读入一个数据页,那肯定是要淘汰一个旧数据页的。

InnoDB内存管理

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

  • 基本LRU算法 TODO

InnoDB管理BP的LRU算法,是用链表实现的:

  • state1,链表头部是P1,表示P1是最近刚被访问过的数据页
  • 此时,一个读请求访问P3,因此变成状态2,P3被移到最前
  • 状态3表示,这次访问的数据页不存在于链表,所以需要在BP中新申请一个数据页Px,加到链表头。但由于内存已满,不能申请新内存。于是清空链表末尾Pm数据页内存,存入Px的内容,放到链表头部

最终就是最久没有被访问的数据页Pm被淘汰。 若此时要做一个全表扫描,会咋样?若要扫描一个200G的表,而这个表是一个历史数据表,平时没有业务访问它。

那么,按此算法扫描,就会把当前BP里的数据全部淘汰,存入扫描过程中访问到的数据页的内容。也就是说BP里主要放的是这个历史数据表的数据。

对于一个正在做业务服务的库,这可不行呀。你会看到,BP内存命中率急剧下降,磁盘压力增加,SQL语句响应变慢。

所以,InnoDB不能直接使用原始的LRU。InnoDB对其进行了优化。

  • 改进的LRU算法

InnoDB按5:3比例把链表分成New区和Old区。图中LRU_old指向的就是old区域的第一个位置,是整个链表的5/8处。即靠近链表头部的5/8是New区域,靠近链表尾部的3/8是old区域。

改进后的LRU算法执行流程:

  1. 状态1,要访问P3,由于P3在New区,和优化前LRU一样,将其移到链表头部 =》状态2
  2. 之后要访问一个新的不存在于当前链表的数据页,这时依然是淘汰掉数据页Pm,但新插入的数据页Px,是放在LRU_old
  3. 处于old区的数据页,每次被访问的时候都要做如下判断:若该数据页在LRU链表中存在的时间超过1s,就把它移动到链表头部若该数据页在LRU链表中存在的时间短于1s,位置保持不变。1s是由参数innodb_old_blocks_time控制,默认值1000,单位ms。

该策略,就是为了处理类似全表扫描的操作量身定制。还是扫描200G历史数据表:

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

可以看到,这个策略最大的收益,就是在扫描这个大表的过程中,虽然也用到了BP,但对young区完全没有影响,从而保证了Buffer Pool响应正常业务的查询命中率。

总结

MySQL采用的是边算边发的逻辑,因此对于数据量很大的查询结果来说,不会在server端保存完整的结果集。所以,如果客户端读结果不及时,会堵住MySQL的查询过程,但是不会把内存打爆。

而对于InnoDB引擎内部,由于有淘汰策略,大查询也不会导致内存暴涨。并且,由于InnoDB对LRU算法做了改进,冷数据的全表扫描,对Buffer Pool的影响也能做到可控。

全表扫描还是比较耗费IO资源的,所以业务高峰期还是不能直接在线上主库执行全表扫描的。

参考

《MySQL实战45讲》

相关推荐

Github霸榜的SpringBoot全套学习教程,从入门到实战,内容超详细

前言...

SpringBoot+LayUI后台管理系统开发脚手架

源码获取方式:关注,转发之后私信回复【源码】即可免费获取到!项目简介本项目本着避免重复造轮子的原则,建立一套快速开发JavaWEB项目(springboot-mini),能满足大部分后台管理系统基础开...

Spring Boot+Vue全栈开发实战,中文版高清PDF资源

SpringBoot+Vue全栈开发实战,中文高清PDF资源,需要的可以私我:)SpringBoot致力于简化开发配置并为企业级开发提供一系列非业务性功能,而Vue则采用数据驱动视图的方式将程序...

2021年超详细的java学习路线总结—纯干货分享

本文整理了java开发的学习路线和相关的学习资源,非常适合零基础入门java的同学,希望大家在学习的时候,能够节省时间。纯干货,良心推荐!第一阶段:Java基础...

探秘Spring Cache:让Java应用飞起来的秘密武器

探秘SpringCache:让Java应用飞起来的秘密武器在当今快节奏的软件开发环境中,性能优化显得尤为重要。SpringCache作为Spring框架的一部分,为我们提供了强大的缓存管理能力,让...

3,从零开始搭建SSHM开发框架(集成Spring MVC)

目录本专题博客已共享在(这个可能会更新的稍微一些)https://code.csdn.net/yangwei19680827/maven_sshm_blog...

Spring Boot中如何使用缓存?超简单

SpringBoot中的缓存可以减少从数据库重复获取数据或执行昂贵计算的需要,从而显著提高应用程序的性能。SpringBoot提供了与各种缓存提供程序的集成,您可以在应用程序中轻松配置和使用缓...

我敢保证,全网没有再比这更详细的Java知识点总结了,送你啊

接下来你看到的将是全网最详细的Java知识点总结,全文分为三大部分:Java基础、Java框架、Java+云数据小编将为大家仔细讲解每大部分里面的详细知识点,别眨眼,从小白到大佬、零基础到精通,你绝...

1,从零开始搭建SSHM开发框架(环境准备)

目录本专题博客已共享在https://code.csdn.net/yangwei19680827/maven_sshm_blog1,从零开始搭建SSHM开发框架(环境准备)...

做一个适合二次开发的低代码平台,把程序员从curd中解脱出来-1

干程序员也有好长时间了,大多数时间都是在做curd。现在想做一个通用的curd平台直接将我们解放出来;把核心放在业务处理中。用过代码生成器,在数据表设计好之后使用它就可以生成需要的controller...

设计一个高性能Java Web框架(java做网站的框架)

设计一个高性能JavaWeb框架在当今互联网高速发展的时代,构建高性能的JavaWeb框架对于提升用户体验至关重要。本文将从多个角度探讨如何设计这样一个框架,让我们一起进入这段充满挑战和乐趣的旅程...

【推荐】强&牛!一款开源免费的功能强大的代码生成器系统!

今天,给大家推荐一个代码生成器系统项目,这个项目目前收获了5.3KStar,个人觉得不错,值得拿出来和大家分享下。这是我目前见过最好的代码生成器系统项目。功能完整,代码结构清晰。...

Java面试题及答案总结(2025版持续更新)

大家好,我是Java面试分享最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试场景题及答案。...

Java开发网站架构演变过程-从单体应用到微服务架构详解

Java开发网站架构演变过程,到目前为止,大致分为5个阶段,分别为单体架构、集群架构、分布式架构、SOA架构和微服务架构。下面玄武老师来给大家详细介绍下这5种架构模式的发展背景、各自优缺点以及涉及到的...

本地缓存GuavaCache(一)(guava本地缓存原理)

在并发量、吞吐量越来越大的情况下往往是离不开缓存的,使用缓存能减轻数据库的压力,临时存储数据。根据不同的场景选择不同的缓存,分布式缓存有Redis,Memcached、Tair、EVCache、Aer...