从云数据迁移服务看MySQL大表抽取模式
摘要:MySQL JDBC抽取到底应该采用什么样的方式,且听小编给你娓娓道来。
小编最近在云上的一个迁移项目中被MySQL抽取模式折磨的很惨。一开始爆内存被客户怼,再后来迁移效率低下再被怼。MySQL JDBC抽取到底应该采用什么样的方式,且听小编给你娓娓道来。
Java-JDBC通信原理
JDBC与数据库之间的通信是通过socket完,大致流程如下图所示。Mysql Server ->内核Socket Buffer -> 客户端Socket Buffer ->JDBC所在的JVM
JDBC读取数据的三种模式
方式1:使用JDBC默认参数读取数据
主要分为以下几步:
1)Mysql Server通过OuputStream 向 Socket Server 本地Kennel Buffer 写入数据,这里是一次内存拷贝。
2)当Socket Server 本地Kennel Buffer 有数据,就会通过TCP链路把数据传输到Socket Client 所在机器的Kennel Buffer。
3)JDBC 所在JVM利用InputSream读取本地Kennel Buffer 数据到JVM内存,没有数据时,则读取被阻塞。
接下来就是不断重复1,2,3的过程。问题是,Socket Client 端的JVM在默认模式下读取Kennel Buffer是没有考虑本机内存大小的,有多少读多少。如果数据太大,就会造成FULL GC,紧接着内存溢出。
参考 JDBC API docs,默认模式 Java demo 代码如下
public static Connection getConnection() throws SQLException {
Properties connectionProps = new Properties();
connectionProps.put("user", this.userName);
connectionProps.put("password", this.password);
Connection conn = DriverManager.getConnection(
"jdbc:" + this.dbms + "://" + "127.0.0.1:3306",
connectionProps);
return conn;
} public static void viewTable(Connection con) throws SQLException {
String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";
Connection conn = getConnection();
try (Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
// ...
}
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
}
方式2:游标查询
为了解决方式1爆内存的问题,JDBC提供了一个游标参数,在建立jdbc连接时加上 useCursorFetch=true。设置游标后,JDBC 每次会告诉Server端每次抽取的数据量,避免爆内存。通信过程如下图所示。
方式2游标查询虽然解决了内存溢出的问题,方式2极大的依赖网络质量。当网络时延增大,假设每次通信增加10ms,10万次通信就会多出1000s。这里仅仅是每次发请求的RT,TCP每次发送报文,都要求反馈ACK保证数据可靠性。client每取100行(请求行数可配置),就会有多次通信,进一步放大时延增加导致的效率问题。此外,游标查询下,Mysql无法预知查询的结束时延,为了应对自身的DML操作会在本地建立一个临时空间存放要抽取的数据。因此,游标查询时会有以下几个现象发生,
- IOPS飙升,Mysql将数据写入到临时空间,数据传输时从临时空间读取数据,这都会引发大量IO操作。
- 磁盘空间飙升,临时空间生命周期存在于整个JDBC读取阶段,直到客户端发起Result.close()时才会被Mysql回收。
- CPU和内存有一定比例上升。
有关游标查询的原理可参考博客MySQL JDBC StreamResult通信原理浅析以及JDBC源码,本文不在赘述。
参考 JDBC API docs,游标模式 Java demo 代码如下
public static Connection getConnection() throws SQLException {
Properties connectionProps = new Properties();
connectionProps.put("user", this.userName);
connectionProps.put("password", this.password);
Connection conn = DriverManager.getConnection(
"jdbc:" + this.dbms + "://" + "127.0.0.1:3306?"
+"useCursorFetch=true",
connectionProps);
return conn;
} public static void viewTable(Connection con) throws SQLException {
String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";
Connection conn = getConnection();
try (PrepareStatement stmt = conn.prepareStatement(query)) {
stmt.setFetchSize(100);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
// ...
}
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
}
方式3: Stream读取数据
方式1会导致JVM内存溢出,方式2虽然不会FULL GC但是通信效率较低,而且也会导致Mysql服务端IOPS飙升,消耗磁盘空间等问题。因此,我们介绍Stream读取数据 ,流式需要在读取Result前设置
statement.setFetchSize(Integer.MIN_VALUE)
方式3在通信前不会做任何Server-Cient的交互操作,避免通信效率低下。服务端准备好数据写入Server的Kennel Buffer中,这些数据通过TCP链路传输到Client的Kennel Buffer中,紧接着client端inputStream.read()方法被唤醒去读取数据,与方式1不同,client每次只会读取一个package大小的数据,如果一个package不满一行则会再读取一个package。当client消费数据的速度不及数据传输速率时,client端kennel区的数据就会被堆满,紧接着Server端的kennel数据也会堆满进而阻塞了OuputStream。这样,JDBC在Stream模式下就像一个水管连接两个蓄水池,Client和Server达到一个平衡。
对 于JDBC客户端,由于每次都是从kennel读取数据,效率会比方式2高很多,每次读取一小部分数据也不会导致JVM内存溢出。对于服务端,Mysql每次都是往kennel写数据,无需建立临时空间,不涉及IO读取,服务端压力也变小了。当然,方式3也有自己的问题,例如Stream流式时无法cancel,cancel不阻塞等等。
参考 JDBC API docs,网上很多教程需要设置useCursorFetch=true,ResultSet.FETCH_REVERSE等,其实小编研究完JDBC驱动源码后发现,只需要设fetchSize=Integer. MIN_VALUE,其他配置均和默认配置保持一致即可。游标模式 Java demo 代码如下,
public static Connection getConnection() throws SQLException {
Properties connectionProps = new Properties();
connectionProps.put("user", this.userName);
connectionProps.put("password", this.password);
Connection conn = DriverManager.getConnection(
"jdbc:" + this.dbms + "://" + "127.0.0.1:3306",
connectionProps);
return conn;
} public static void viewTable(Connection con) throws SQLException {
String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";
Connection conn = getConnection();
try (PrepareStatement stmt = conn.prepareStatement(query)) {
statement.setFetchSize(Integer.MIN_VALUE)
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
// ...
}
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
}
云数据迁移服务在三种模式下的调优
云数据迁移服务(Cloud Data Migration, CDM)是华为云上一个迁移工具,详见CDM官网,小编则通过CDM介绍如何切换三种模式抽取数据。CDM默认使用的是方式3,流式抽取数据,如果需要切换方式1,方式2需额外配置。
配置方式1:默认读取
新建Mysql连接器,建立方法详见官网,在高级属性中增加useCursorFetch=false和adopt.stream=false
配置方式2:游标查询
编辑Mysql连接器,在高级属性中增加useCursorFetch=true和adopt.stream=false。游标查询的大小可通过界面上的Fetch Size调整,默认1000。
配置方式3:流式
CDM默认走的流式,无需额外配置。注意Stream模式下,界面上的`Fetch Size`是不起作用的,原因参考上一节。
性能对比
新建Mysql2Hive的CDM迁移作业,源表101个字段,100万行数据,配置如下
方式1:写入100万行数据耗时1m22s
方式2:同样写入100万行,调整fetchSzie分别为1,10,100,100,最低耗时2m1s
方式3:同样写入100万行,耗时1m5s
小编还测试了100万的小表,明显方式1和方式3的速率要远远高于方式2,另外小编还测试了1000万的大表,方式1爆内存,方式2正常迁移但耗时20分钟以上,而方式3仍然可以在15分钟内跑完。
本文分享自华为云社区《从云数据迁移服务看MySQL大表抽取模式》,原文作者:Leef724。
从云数据迁移服务看MySQL大表抽取模式的更多相关文章
- 优秀后端架构师必会知识:史上最全MySQL大表优化方案总结
本文原作者“ manong”,原创发表于segmentfault,原文链接:segmentfault.com/a/1190000006158186 1.引言 MySQL作为开源技术的代表作之一,是 ...
- MySQL 大表优化方案(长文)
当MySQL单表记录数过大时,增删改查性能都会急剧下降,可以参考以下步骤来优化: 单表优化 除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑.部署.运维的各种复杂度,一般以整型 ...
- [记录]一则清理MySQL大表以释放磁盘空间的案例
一则清理MySQL大表以释放磁盘空间的案例 一.基本情况: 1.dbtest库554G,先清理st_online_time_away_ds(37G)表的数据,保留半年的数据: 1)删除的数据:sele ...
- mysql 大表mysqldump迁移方案
场景 一张历史表product_history 500万数据,凌晨的才会将正式表的数据迁移到历史表,此次需求将历史表迁移到一个更便宜的数据库实例进行存储. 条件 1.此表不是实时写,凌晨才会更新 2. ...
- 详解MySQL大表优化方案( 转)
当MySQL单表记录数过大时,增删改查性能都会急剧下降,可以参考以下步骤来优化: 单表优化 除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑.部署.运维的各种复杂度,一般以整型 ...
- MySQL 大表优化方案探讨
当MySQL单表记录数过大时,增删改查性能都会急剧下降,可以参考以下步骤来优化: 单表优化 除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑.部署.运维的各种复杂度,一般以整型 ...
- MySQL大表优化方案
转:https://segmentfault.com/a/1190000006158186?hmsr=toutiao.io&utm_medium=toutiao.io&utm_sour ...
- MySQL 大表优化方案
当MySQL单表记录数过大时,增删改查性能都会急剧下降,可以参考以下步骤来优化: 单表优化 除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑.部署.运维的各种复杂度,一般以整型 ...
- MySQL大表优化方案 Mysql的row_format(fixed与dynamic)
转自:https://mp.weixin.qq.com/s/VY69wWlrVLjRtKU7ULrYGw 当MySQL单表记录数过大时,增删改查性能都会急剧下降,可以参考以下步骤来优化: 单表优化 除 ...
随机推荐
- JAVA_基础反射机制
Java反射机制概述 Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期 借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内 部属性及方法. 加 ...
- 风炫安全WEB安全学习第二十五节课 利用XSS键盘记录
风炫安全WEB安全学习第二十五节课 利用XSS键盘记录 XSS键盘记录 同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源.所以xyz.com下的js脚本采用a ...
- pytorch模型结构可视化,可显示每层的尺寸
最近在学习一些检测方面的网络,使用的是pytorch.模型结构可视化是学习网络的有用的部分,pytorch没有原生支持这个功能,需要找一些其他方式,下面总结几种方法(推荐用4). 1. torch . ...
- Java高并发与多线程(一)-----概念
其实之前一直想专门写一篇,单独说一说Java的多线程与高并发,但是一直以来,都没有想到能够用什么比较有趣的表现形式去表达出来,而且网上充斥着很多类似的博客,有好的又不好的,有简介的有繁琐的,所以也一直 ...
- .NET Core学习笔记(9)——Entity Framework Core之Code First
上篇我们介绍了怎么通过已有的SQLServer表来创建实体类,本篇我们改用Code First的方式,由C#代码书写的实体类来生成SQLServer表.并且通过简单的Console APP往SQLSe ...
- 【Git】3、创建Git版本库、配置Git仓库用户邮箱信息
初识Git 文章目录 初识Git 1.创建Git版本库 认识.git 2.基础配置 2.1.查看配置信息 2.2.配置昵称邮箱信息 2.3.修改配置信息 1.通过命令行 2.通过修改配置文件. 修改全 ...
- 敏感信息泄露 - Pikachu
概述: 由于后台人员的疏忽或者不当的设计,导致不应该被前端用户看到的数据被轻易的访问到. 比如:---通过访问url下的目录,可以直接列出目录下的文件列表;---输入错误的url参数后报错信息里面包含 ...
- 源代码增强的一点说明(souce code enhance )
souce code enhance 分为显式和隐式两种. 下面以显式创建为例子: 1.在ABAP编辑器中, 打开想要编辑的程序,切换到可编辑模式 2.在源代码中的指定位置右键,弹出菜单,选择 Enh ...
- 入门OJ:简单的网络游戏
题目描述 在某款极具技术含量的网络游戏中,佳佳靠着他的聪明智慧垄断了游戏中的油田系统.油田里有许多油井,这些油井排成一个M*N的矩形.每个油井都有一个固定的采油量.每两个相邻的油井之间有一条公路,这些 ...
- 24V转3.3V稳压芯片,高效率同步降压DC-DC变换器3A输出电流
PW2312是一个高频,同步,整流,降压,开关模式转换器与内部功率MOSFET.它提供了一个非常紧凑的解决方案,以实现1.5A的峰值输出电流在广泛的输入电源范围内,具有良好的负载和线路调节. PW23 ...