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

MySQL数据查询太多会OOM吗?

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

线上 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讲》

相关推荐

史上最全的 Python 学习资料,PDF 电子书大合集(免费)

史上最全的Python学习资料最近一直有读者问我有没有好的Python学习资料,有没有好的PDF电子书。小白学习Python也有很长的时间了,搜集的资料也有非常多。今天整理了下,全部免...

16 款最佳免费开源 PDF 开发库 | OpenSSH 9.9 发布,默认禁用 DSA 算法

16款最佳免费开源PDF开发库便携式文档格式(PDF)由AdobeSystems于1993年创建,用于文档交换。这种格式包括PostScript页面描述编程语言的子集、字体嵌...

?? 盘点Python入门必备神仙书籍|附pdf

Python编程从入门到实践整本书内容非常丰富,包含python相关基础知识和项目实践部分,基础知识部分基本上讲解了所有python相关的基础知识,实践部分选择了三个案例。非常适合初学者学习Pyth...

Python 3.9.0 官方中文PDF文档,建议收藏,拿走不谢

最新版的Python官方文档!整理好了。领取方式见文末...

Python趣味打怪:60秒学会一个例子,147段代码助你从入门到大师

鱼羊发自凹非寺量子位报道|公众号QbitAI人生苦短,编程苦手,不妨学起Python,感受一飞冲天的快乐。不要害怕学习的过程枯燥无味,这里有程序员jackzhenguo打造的一份中文Pyt...

咸鱼疯传3.6W次!Python16本高清版 PDF初学者的极佳教材荐

【文末有获取方式】【文末有获取方式】...

史上最详细python学习路线-从入门到精通,只需5个月时间

Python是一种功能很强大的语言,对于零基础学习Python还是有难度的,但只要学习方法对,入门还是很快哒。针对Python的初学者,从无到有的Python语言如何入门,主要包括了:Python的简...

硬核!288页Python核心知识笔记(附思维导图,建议收藏)

今天就给大家分享一份288页Python核心知识笔记,相较于部分朋友乱糟糟的笔记,这份笔记更够系统地总结相关知识,巩固Python知识体系。文末获取完整版PDF...

这张思维导图,涵盖Python所有核心知识点,PDF赶快拿走

这张Python思维导图,涵盖了所有的核心知识点,包括基础知识、爬虫、函数、模块、类和对象等,从以往假大空的知识堆砌转变为清晰实用的知识体系...

顶级黑客用5分钟爬的python教程!整整400集,建议收藏

兄弟!毫无套路!!福利分享:1、本套视频一共400集,本套视频共分4季第一季Python基础。...

自学python必看的书籍《Python编程:从入门到实践》PDF拿走不谢

目录...

《Python知识手册》,高清全彩pdf版开放下载

Python编程还不懂?今天我要把我参与编写的这套《Python知识手册》免费分享出来,看完文末有惊喜哦。文末惊喜,记得看完哦!...

1张思维导图,告诉你Python核心知识体系,高清PDF拿走不谢

这张思维导图按顺序依次展示了以下内容的核心知识:基础知识、函数、文件、类和对象、魔方方法、模块、爬虫等内容。每一部分都细致展开解读,化复杂为具体,化零散为整体,尤其适合Python初学者参考学习。Py...

Python 创始人官方发布整套中文PDF文档,足足27本,学习必备!

1...

清华教授熬夜开发的《Python王者归来》高清版PDF开放下载

相信通过这本资料你可以轻松学会Python语法与应用,并逐步向Python高手之路迈进!...