搜索
首页数据库mysql教程浅谈MySQL JDBC StreamResult通信原理

浅谈MySQL JDBC StreamResult通信原理

Oct 19, 2018 pm 04:33 PM
javajdbcmysql数据库线程

本篇文章给大家带来的内容是关于浅谈MySQL JDBC StreamResult通信原理,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

使用MySQL JDBC读取过较大数据量的人应该清楚(例如超过1GB),在读取的时候内存很可能会Java堆内存溢出,而我们的解决方案是statement.setFetchSize(Integer.MIN_VALUE)并确保游标是只读向前滚动的即可(为游标的默认值),也可以强制类型转换为com.mysql.jdbc.StatementImpl,然后调用其内部方法:enableStreamingResults()这样读取数据内存就不会挂掉了,这两者达到的效果是一致的。当然也可以使用useCursorFetch,但是这种方式测试结果性能要比StreamResult慢很多,为什么?在本文会阐述其大致的原理。

我在前面的部分文章和书籍中都有介绍过其MySQL JDBC在这一块内部处理的代码分成三个不同的类来完成的,不过我一直没有去深究过数据库和JDBC之间到底是如何通信的过程。有一段时间我一直认为这都属于服务端行为或者是客户端与服务端配合的行为,然后并不其然,今天我们来讲一下这个行为是怎么回事。

【先回顾一下简单的通信】:

JDBC与数据库之间的通信是通过Socket完成的,因此我们可以把数据库当成一个SocketServer的提供方,因此当SocketServer返回数据的时候(类似于SQL结果集的返回)其流程是:服务端程序数据(数据库) -> 内核Socket Buffer -> 网络 -> 客户端Socket Buffer -> 客户端程序(JDBC所在的JVM内存)

到目前为止,IT行业中大家所看到的JDBC无论是:MySQL JDBC、SQL Server JDBC、PG JDBC、Oracle JDBC。甚至于是NoSQL的Client:Redis Client、MongoDB Client、Memcached,数据的返回基本也是这样一个逻辑。

2509688-08682973734cd3ee.png

【使用MySQL JDBC默认直接读取数据为什么会挂?】

(1)MySQL Server方在发起的SQL结果集会全部通过OutputStream向外输出数据,也就是向本地的Kennel对应的socket buffer中写入数据,这是一次内存拷贝(内存拷贝这个不是本文的重点)。

(2)此时Kennel的Buffer有数据的时候就会把数据通过TCP链路(JDBC主动发起的Socket链路),回传数据,此时数据会回传到JDBC所在机器上,会先进入Kennel区域,同样进入到一个Buffer区。

(3)JDBC在发起SQL操作后,Java代码是在inputStream.read()操作上阻塞,当缓冲区有数据的时候,就会被唤醒,然后将缓冲区的数据读取到Java内存中,这是JDBC端的一次内存拷贝。

(4)接下来MySQL JDBC会不断读取缓冲区数据到Java内存中,MySQL Server会不断发送数据。注意在数据没有完全组装完之前,客户端发起的SQL操作不会响应,也就是给你的感觉MySQL服务端还没响应,其实数据已经到本地,JDBC还没对调用execute方法的地方返回结果集的第一条数据,而是不断从缓冲器读取数据。

(5)关键是这个傻帽就像一把这个数据读取完,根本不管家里放不放的下,就会将整个表的内容读取到Java内存中,先是FULL GC,接下来就是内存溢出。

【JDBC参数上设置useCursorFetch=true可以解决】

这个方案配合FetchSize设置,确实可以解决问题,这个方案其实就是告诉MySQL服务端我要多少数据,每次要多少数据,通信过程有点像这样:

2509688-f1a76be8ea4da3f9.png

这样做就像我们生活中的那样,我需要什么就去超市买什么,需要多少就去买多少。不过这种交互不像现在网购,坐在家里就可以把东西送到家里来,它一定要走路(网络链路),也就是需要网络的时间开销,假如数据有1亿数据,将FetchSize设置成1000的话,会进行10万次来回通信;如果网络延迟同机房0.02ms,那么10万次通信会增加2秒的时间,不算大。那么如果跨机房2ms的延迟时间会多出来200秒(也就是3分20秒),如果国内跨城市10~40ms延迟,那么时间将会1000~4000秒,如果是跨国200~300ms呢?时间会多出十多个小时出来。

在这里的计算中,我们还没有包含系统调用次数增加了很多,线程等待和唤醒的上下文次数变多,网络包重传的情况对整体性能的影响,因此这种方案看似合理,但是性能确不怎么样。

另外,由于MySQL方不知道客户端什么时候将数据消费完,而自身的对应表可能会有DML写入操作,此时MySQL需要建立一个临时表空间来存放需要拿走的数据。因此对于当你启用useCursorFetch读取大表的时候会看到MySQL上的几个现象:

(1)IOPS飙升,因为存在大量的IO读取,如果是普通硬盘,此时可能会引起业务写入的抖动

(2)磁盘空间飙升,这块临时空间可能比原表更大,如果这个表在整个库内部占用相当大的比重有可能会导致数据库磁盘写满,空间会在结果集读取完成后或者客户端发起Result.close()时由MySQL去回收。

(3)CPU和内存会有一定比例的上升,根据CPU的能力决定。

(4)客户端JDBC发起SQL后,长时间等待SQL响应数据,这段时间就是服务端在准备数据,这个等待与原始的JDBC不设置任何参数的方式也表现出等待,在内部原理上是不一样的,前者是一直在读取网络缓冲区的数据,没有响应给业务,现在是MySQL数据库在准备临时数据空间,没有响应给JDBC。

【Stream读取数据】

我们知道第1种方式会导致Java挂掉,第2种方式效率低而且对MySQL数据库的影响较大,客户端响应也较慢,仅仅能够解决问题而已,那么现在来看下Stream读取方式。

前面提到当你使用statement.setFetchSize(Integer.MIN_VALUE)或com.mysql.jdbc.StatementImpl.enableStreamingResults()就可以开启Stream读取结果集的方式,在发起execute之前FetchSize不能再手工设置,且确保游标是FORWARD_ONLY的。

这种方式很神奇,似乎内存也不挂了,响应也变快了,对MySQL的影响也变小了,至少IOPS不会那么大了,磁盘占用也没有了。以前仅仅看到JDBC中走了单独的代码,认为这是MySQL和JDBC之间的另一种通信协议,殊不知,它竟然是“客户端行为”,没错,你没看错,它就是客户端行为。

它在发起enableStreamingResults()的时候,几乎不会做任何与服务端的交互工作,也就是服务端会按照方式1回传数据,那么服务端使劲向缓冲区怼数据,客户端是如何扛得住压力的呢?

在JDBC当中,当你开启Stream结果集处理的时候,它并不是一把将所有数据读取到Java内存中的,也就是图1中并不是一次性将数据读取到Java缓冲区的,而是每次读取一个package(这个package可以理解成Java中的一个byte[]数组),一次最多读取这么多,然后会看是否继续向下读取保证数据的完整性。业务代码是按照字节解析成行也业务方使用的。

服务端刚开始使劲向缓冲区怼数据,这些数据也会怼满客户端的内核缓冲区,当两边的缓冲区都被怼满的时候,服务端的1个Buffer尝试通过TCP传递数据给接收方时,此时由于消费方的缓冲区也是满的,因此发送方的线程会阻塞住,等待对方消费,对方消费一部分,就可以推送一部分数据过去。连起来看就是JDBC的Stream数据未来得及消费之前,缓冲区数据如果是满的,那么MySQL发送数据的线程就阻塞住了,这样确保了一个平衡(关于这一点,大家可以使用Java的Socket来尝试下是否是这样的)。

对于JDBC客户端,数据获取的时候每次都在本地的内核缓冲区当中,就在小区的快递包裹箱拿回家一个距离,那么自然比起每次去超市的RT要小得多了,而且这个过程是准备好的数据,所以没有IO阻塞的过程(除非MySQL服务端传递的数据还不如消费端处理数据来得快,那一般也只有消费端不做任何业务,拿到数据直接放弃的测试代码,才会发生这样的事情),这个时候不论:跨机房、跨地区、跨国家,只要服务端开始响应就会源源不断地传递数据过来,而这个动作即使是第1种方式也是必然需要经历的过程。

相对于第1种方式,JDBC使用的时候会不导致内存溢出,即使读取大表不内存溢出也会很长时间才会响应;不过这种方式相对方式1来讲对数据库影响相对较大,在传递的数据的过程中,相应的数据行会被上锁(防止被修改),使用InnoDB会分段加锁处理,使用MyISAM会加全表锁,可能导致业务阻塞。

【理论上可以更进一步,只要你愿意】

理论上这种方式是比较好的了,但是就完美主义来讲,我们可以继续探讨一下,对于懒人来讲,我们连到小区楼下快递包裹箱去拿一下的动力也是没有的,我们心里想的就是要是谁给我拿到家里来送到我嘴巴里,连嘴巴都给我掰开多好。

在技术上理论上确实可以做到这样,因为JDBC从内核拷贝内存到Java当中是需要花时间的,要是有另一个人把这个事情做了,我在家里干别的事情的时候它就给我送到家里来了,我要用的时候就直接从家里来,这个时间岂不是省掉了。每错,对于你来讲确实省掉了,不过问题就是谁来送?

在程序中一定需要加一个线程来干这个事情,把内核的数据拷贝到应用内存,甚至于解析成行数据,应用程序直接使用,但这一定完美吗?其实这个中间就有个协调问题了,例如家里要炒菜,缺一包调料,原本可以自己到楼下买,但是非要让别人送家里,这个时候其它的菜都下锅了,就剩一包调料,那么你没别的办法,只能等这包调料送到家里来以后才能进行炒菜的下一道工序。所以,在理想情况下,它可以节约很多次内存拷贝时间,会增加一些协调锁的开销。

那么可以不可以直接从内核缓冲区读取数据呢?

理论上也是可以的,在解释这个问题之前,我们先了解下除了这一次内存拷贝还有那些:

JDBC按照二进制将内核缓冲区的数据读取后,也会进一步解析成具体的结构化数据,由于此时要给业务方返回ResultSet的具体行的结构化数据,也就是生成RowData的数据一定会有一次拷贝,而且JDBC返回某些对象类型数据的时候(例如byte []数组),在某些场景的实现,它不希望你通过结果集修改返回结果中的byte []的内容(byte[1] = 0xFF)去修改ResultSet本身内容,可能还会再做1次内存拷贝,业务代码使用过程中还会存在拼字符串,网络输出等,又是一堆的内存拷贝,这些在业务层面是无法避免的,相对这点点拷贝来讲,简直微不足道,所以我们也没去干这事情,以为从整体上看几乎微不足道,除非你的程序瓶颈在这里。

因此从整体上看内存拷贝是无法避免的,多的这一次无非是系统级的调用,开销会更大一点,从技术上来讲,我们是可以做到直接从内核态直接读取数据的;但这个时候就需要按照字节将Buffer从的数据拿走才能让远程更多的数据传递过来,没有第三个位置存放Buffer了,否则又回到了内核到应用的内存拷贝上来了。

相对来讲,服务端倒是可以优化直接将数据通过直接IO的方式传递(不过这种方式数据的协议就和数据的存储格式一致了,显然只是理论上的), 要真正做到自定义的协议,又要通过内核态数据直接发送,需要通过修改OS级别的文件系统协议,来达到转换的目的。


以上是浅谈MySQL JDBC StreamResult通信原理的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:segmentfault思否。如有侵权,请联系admin@php.cn删除
MySQL:世界上最受欢迎的数据库的简介MySQL:世界上最受欢迎的数据库的简介Apr 12, 2025 am 12:18 AM

MySQL是一种开源的关系型数据库管理系统,主要用于快速、可靠地存储和检索数据。其工作原理包括客户端请求、查询解析、执行查询和返回结果。使用示例包括创建表、插入和查询数据,以及高级功能如JOIN操作。常见错误涉及SQL语法、数据类型和权限问题,优化建议包括使用索引、优化查询和分表分区。

MySQL的重要性:数据存储和管理MySQL的重要性:数据存储和管理Apr 12, 2025 am 12:18 AM

MySQL是一个开源的关系型数据库管理系统,适用于数据存储、管理、查询和安全。1.它支持多种操作系统,广泛应用于Web应用等领域。2.通过客户端-服务器架构和不同存储引擎,MySQL高效处理数据。3.基本用法包括创建数据库和表,插入、查询和更新数据。4.高级用法涉及复杂查询和存储过程。5.常见错误可通过EXPLAIN语句调试。6.性能优化包括合理使用索引和优化查询语句。

为什么要使用mysql?利益和优势为什么要使用mysql?利益和优势Apr 12, 2025 am 12:17 AM

选择MySQL的原因是其性能、可靠性、易用性和社区支持。1.MySQL提供高效的数据存储和检索功能,支持多种数据类型和高级查询操作。2.采用客户端-服务器架构和多种存储引擎,支持事务和查询优化。3.易于使用,支持多种操作系统和编程语言。4.拥有强大的社区支持,提供丰富的资源和解决方案。

描述InnoDB锁定机制(共享锁,独家锁,意向锁,记录锁,间隙锁,下一键锁)。描述InnoDB锁定机制(共享锁,独家锁,意向锁,记录锁,间隙锁,下一键锁)。Apr 12, 2025 am 12:16 AM

InnoDB的锁机制包括共享锁、排他锁、意向锁、记录锁、间隙锁和下一个键锁。1.共享锁允许事务读取数据而不阻止其他事务读取。2.排他锁阻止其他事务读取和修改数据。3.意向锁优化锁效率。4.记录锁锁定索引记录。5.间隙锁锁定索引记录间隙。6.下一个键锁是记录锁和间隙锁的组合,确保数据一致性。

MySQL查询性能差的常见原因是什么?MySQL查询性能差的常见原因是什么?Apr 12, 2025 am 12:11 AM

MySQL查询性能不佳的原因主要包括没有使用索引、查询优化器选择错误的执行计划、表设计不合理、数据量过大和锁竞争。 1.没有索引导致查询缓慢,添加索引后可显着提升性能。 2.使用EXPLAIN命令可以分析查询计划,找出优化器错误。 3.重构表结构和优化JOIN条件可改善表设计问题。 4.数据量大时,采用分区和分表策略。 5.高并发环境下,优化事务和锁策略可减少锁竞争。

您什么时候应该使用复合索引与多个单列索引?您什么时候应该使用复合索引与多个单列索引?Apr 11, 2025 am 12:06 AM

在数据库优化中,应根据查询需求选择索引策略:1.当查询涉及多个列且条件顺序固定时,使用复合索引;2.当查询涉及多个列但条件顺序不固定时,使用多个单列索引。复合索引适用于优化多列查询,单列索引则适合单列查询。

如何识别和优化MySQL中的慢速查询? (慢查询日志,performance_schema)如何识别和优化MySQL中的慢速查询? (慢查询日志,performance_schema)Apr 10, 2025 am 09:36 AM

要优化MySQL慢查询,需使用slowquerylog和performance_schema:1.启用slowquerylog并设置阈值,记录慢查询;2.利用performance_schema分析查询执行细节,找出性能瓶颈并优化。

MySQL和SQL:开发人员的基本技能MySQL和SQL:开发人员的基本技能Apr 10, 2025 am 09:30 AM

MySQL和SQL是开发者必备技能。1.MySQL是开源的关系型数据库管理系统,SQL是用于管理和操作数据库的标准语言。2.MySQL通过高效的数据存储和检索功能支持多种存储引擎,SQL通过简单语句完成复杂数据操作。3.使用示例包括基本查询和高级查询,如按条件过滤和排序。4.常见错误包括语法错误和性能问题,可通过检查SQL语句和使用EXPLAIN命令优化。5.性能优化技巧包括使用索引、避免全表扫描、优化JOIN操作和提升代码可读性。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
4 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器