视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
关于JDBC与MySQL临时表空间的深入解析
2020-11-09 20:24:29 责编:小采
文档


2.检查驱动对 mysql 的设置

我们上一步看到,sql 手工执行会返回错误,但是 jdbc 不返回错误,导致连接一直 sleep,怀疑是 mysql 驱动做了特殊设置,驱动连接 mysql,通过 general_log 查看做了哪些设置。未发现做特殊设置。

3.测试 JDBC 连接

问题的背景中有对JDBC做特殊配置:useCursorFetch=true,不知道是否与隐藏报错有关,接下来进行测试:

发现以下现象:

·加参数 useCursorFetch=true时,做同样的查询确实不会报错

这个参数是为了防止返回结果集过大而采用分段读取的方式。即程序下发一个 sql 给 mysql 后,会等 mysql 可以读结果的反馈,由于 mysql 在执行sql时,返回结果达到 ibtmp 上限后报错,但没有关闭该线程,该线程处理 sleep 状态,程序得不到反馈,会一直等,没有报错。如果 kill 这个线程,程序则会报错。

·不加参数 useCursorFetch=true时,做同样的查询则会报错

结论

1.正常情况下,sql 执行过程中临时表大小达到 ibtmp 上限后会报错;

2.当JDBC设置 useCursorFetch=true,sql 执行过程中临时表大小达到 ibtmp 上限后不会报错。

解决方案

进一步了解到使用 useCursorFetch=true 是为了防止查询结果集过大撑爆 jvm;

但是使用 useCursorFetch=true 又会导致普通查询也生成临时表,造成临时表空间过大的问题;

临时表空间过大的解决方案是 ibtmp1 的大小,然而 useCursorFetch=true 又导致JDBC不返回错误。

所以需要使用其它方法来达到相同的效果,且 sql 报错后程序也要相应的报错。除了 useCursorFetch=true 这种段读取的方式外,还可以使用流读取的方式。流读取程序详见附件部分。

·报错对比

·段读取方式,sql 报错后,程序不报错

·流读取方式,sql 报错后,程序会报错

·内存占用对比

这里对比了普通读取、段读取、流读取三种方式,初始内存占用 28M 左右:

·普通读取后,内存占用 100M 多

·段读取后,内存占用 60M 左右

·流读取后,内存占用 60M 左右

补充知识点

MySQL共享临时表空间知识点

MySQL 5.7在 temporary tablespace上做了改进,已经实现将 temporary tablespace 从 ibdata(共享表空间文件)中分离。并且可以重启重置大小,避免出现像以前 ibdata 过大难以释放的问题。
其参数为:innodb_temp_data_file_path

1.表现

MySQL启动时 datadir 下会创建一个 ibtmp1 文件,初始大小为 12M,默认值下会无限扩展:

通常来说,查询导致的临时表(如group by)如果超出 tmp_table_size、max_heap_table_size 大小则创建 innodb 磁盘临时表(MySQL5.7默认临时表引擎为 innodb),存放在共享临时表空间;

如果某个操作创建了一个大小为100 M的临时表,则临时表空间数据文件会扩展到 100M大小以满足临时表的需要。当删除临时表时,释放的空间可以重新用于新的临时表,但 ibtmp1 文件保持扩展大小。

2.查询视图

可查询共享临时表空间的使用情况:

SELECT FILE_NAME, TABLESPACE_NAME, ENGINE, INITIAL_SIZE, TOTAL_EXTENTS*EXTENT_SIZE AS TotalSizeBytes, DATA_FREE,MAXIMUM_SIZE FROM INFORMATION_SCHEMA.FILES WHERE TABLESPACE_NAME = 'innodb_temporary'\G
*************************** 1. row ***************************
 FILE_NAME: /data/mysql5722/data/ibtmp1
TABLESPACE_NAME: innodb_temporary
 ENGINE: InnoDB
 INITIAL_SIZE: 12582912
 TotalSizeBytes: 31457280
 DATA_FREE: 27262976
 MAXIMUM_SIZE: 31457280
1 row in set (0.00 sec)

3.回收方式

重启 MySQL 才能回收

4.大小

为防止临时数据文件变得过大,可以配置该 innodb_temp_data_file_path (需重启生效)选项以指定最大文件大小,当数据文件达到最大大小时,查询将返回错误:

innodb_temp_data_file_path=ibtmp1:12M:autoextend:max:2G

5. 临时表空间与 tmpdir 对比

共享临时表空间用于存储非压缩InnoDB临时表(non-compressed InnoDB temporary tables)、关系对象(related objects)、回滚段(rollback segment)等数据;

tmpdir 用于存放指定临时文件(temporary files)和临时表(temporary tables),与共享临时表空间不同的是,tmpdir存储的是compressed InnoDB temporary tables。

可通过如下语句测试:

CREATE TEMPORARY TABLE compress_table (id int, name char(255)) ROW_FORMAT=COMPRESSED;
CREATE TEMPORARY TABLE uncompress_table (id int, name char(255)) ;

附件

SimpleExample.java

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
public class SimpleExample {
 public static void main(String[] args) throws Exception {
 Class.forName("com.mysql.jdbc.Driver");
 Properties props = new Properties();
 props.setProperty("user", "root");
 props.setProperty("password", "root");
 SimpleExample engine = new SimpleExample();
// engine.execute(props,"jdbc:mysql://10.186.24.31:3336/hucq?useSSL=false");
 engine.execute(props,"jdbc:mysql://10.186.24.31:3336/hucq?useSSL=false&useCursorFetch=true");
 }
 final AtomicLong tmAl = new AtomicLong();
 final String tableName="test";
 public void execute(Properties props,String url) {
 CountDownLatch cdl = new CountDownLatch(1);
 long start = System.currentTimeMillis();
 for (int i = 0; i < 1; i++) {
 TestThread insertThread = new TestThread(props,cdl, url);
 Thread t = new Thread(insertThread);
 t.start();
 System.out.println("Test start");
 }
 try {
 cdl.await();
 long end = System.currentTimeMillis();
 System.out.println("Test end,total cost:" + (end-start) + "ms");
 } catch (Exception e) {
 }
 }
 
 class TestThread implements Runnable {
 Properties props;
 private CountDownLatch countDownLatch;
 String url;
 public TestThread(Properties props,CountDownLatch cdl,String url) {
 this.props = props;
 this.countDownLatch = cdl;
 this.url = url;
 }
 public void run() {
 Connection connection = null;
 PreparedStatement ps = null;
 Statement st = null;
 long start = System.currentTimeMillis();
 try {
 connection = DriverManager.getConnection(url,props);
 connection.setAutoCommit(false);
 st = connection.createStatement();
 
 //st.setFetchSize(500);
 st.setFetchSize(Integer.MIN_VALUE); //仅修改此处即可
 
 ResultSet rstmp;
 
 st.executeQuery("select sum(k) from sbtest1 group by k");
 rstmp = st.getResultSet();
 while(rstmp.next()){
 
 }
 } catch (Exception e) {
 System.out.println(System.currentTimeMillis() - start);
 System.out.println(new java.util.Date().toString());
 e.printStackTrace();
 } finally {
 if (ps != null)
 try {
 ps.close();
 } catch (SQLException e1) {
 e1.printStackTrace();
 }
 if (connection != null)
 try {
 connection.close();
 } catch (SQLException e1) {
 e1.printStackTrace();
 }
 this.countDownLatch.countDown();
 }
 }
 }
}

总结

下载本文
显示全文
专题