最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

MyBatis游標(biāo)Cursor的正確使用和百萬數(shù)據(jù)傳輸?shù)膬?nèi)存測試

 更新時間:2024年01月27日 08:41:02   作者:isea533  
這篇文章主要介紹了MyBatis游標(biāo)Cursor的正確使用和百萬數(shù)據(jù)傳輸?shù)膬?nèi)存測試,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

很早以前為了處理大量數(shù)據(jù)想過使用Cursor,當(dāng)時發(fā)現(xiàn)沒有效果,就沒有繼續(xù)深入。

這次為了搞清楚 Cursor 是否真的有用,找些資料和源碼發(fā)現(xiàn)是有效果的,只是缺了必要的配置。

準(zhǔn)備測試數(shù)據(jù)

創(chuàng)建表:

CREATE TABLE test_table (
  id INT PRIMARY KEY,
  name VARCHAR(20),
  age INT,
    address VARCHAR(200)
);

創(chuàng)建存儲過程:

-- 創(chuàng)建一個存儲過程,用于插入10萬測試數(shù)據(jù)
DELIMITER //
CREATE PROCEDURE insert_test_data()
BEGIN
  DECLARE i INT DEFAULT 1;
  WHILE i <= 100000 DO
    -- 隨機(jī)生成姓名和年齡
    SET @name = CONCAT('name', i);
    SET @address = CONCAT('address......................', i);
    SET @age = FLOOR(RAND() * 100);
    -- 插入數(shù)據(jù)
    INSERT INTO test_table (id, name, age, address) VALUES (i, @name, @age, @address);
    -- 更新計數(shù)器
    SET i = i + 1;
  END WHILE;
END //
DELIMITER ;

插入數(shù)據(jù):

-- 調(diào)用存儲過程
CALL insert_test_data();

準(zhǔn)備測試接口

public interface TestMapper {
    class Person {
        private String name;
        private int age;
        private Integer id;
        private String address;

        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public String getAddress() {
            return address;
        }

        public void setAddress(String address) {
            this.address = address;
        }
    }
    
    //TODO 注意sql中指定了表名 test,如果自己執(zhí)行,需要按需修改
    @Select("select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table")
    @Options(fetchSize = Integer.MIN_VALUE)
    Cursor<Person> selectAll();

    @Select("select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table")
    List<Person> selectList();
}

前面插入10萬數(shù)據(jù),這里union all 10次達(dá)到百萬數(shù)據(jù)。

測試代碼

@Test
public void testCursor() throws InterruptedException {
    //等待10秒方便jvisualVM監(jiān)控
    Thread.sleep(10000);
    long start = System.currentTimeMillis();
    try (SqlSession sqlSession = getSqlSession()) {
        TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
        try(Cursor<TestMapper.Person> cursor = testMapper.selectAll()){

            int total = 0;
            for (TestMapper.Person o : cursor) {
                total++;
            }
            System.out.println("總數(shù): " + total);
        } catch (IOException ignore) {
        }
    }
    System.out.println("耗時: " + (System.currentTimeMillis() - start));
    Thread.sleep(10000);
}


@Test
public void testSelectAll() throws InterruptedException {
    //等待10秒方便jvisualVM監(jiān)控
    Thread.sleep(10000);
    long start = System.currentTimeMillis();
    try (SqlSession sqlSession = getSqlSession()) {
        TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
        List<TestMapper.Person> people = testMapper.selectList();
        System.out.println(people.size());
    }
    System.out.println("耗時: " + (System.currentTimeMillis() - start));
    Thread.sleep(10000);
}

private static SqlSessionFactory sqlSessionFactory;

@BeforeAll
public static void init() {
    try {
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        reader.close();
    } catch (IOException ignore) {
        ignore.printStackTrace();
    }
}

public SqlSession getSqlSession() {
    return sqlSessionFactory.openSession();
}

測試結(jié)果

1.1. 直接List接收100萬數(shù)據(jù)

  • 查詢過程耗時:7833ms
  • GC:21次
  • 占用內(nèi)存:885MB

1.2. 限制500MB內(nèi)存,直接List接收100萬數(shù)據(jù)

增加JVM參數(shù) -Xmx500m

執(zhí)行結(jié)果如下:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded

    at java.lang.StringCoding$StringDecoder.decode(StringCoding.java:149)

內(nèi)存溢出。

2.1. 使用游標(biāo)Cursor,不配置其他參數(shù)

@Select("select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table union all " +
            "select * from test.test_table")
Cursor<Person> selectAll();

  • 查詢過程耗時:5908ms
  • GC:21次
  • 占用內(nèi)存:428MB

使用游標(biāo)的情況在測試中,占用了第1種情況一半的內(nèi)存,處理速度也更快,GC次數(shù)也沒增加。

2.2. 使用游標(biāo)Cursor,不配置其他參數(shù),限制200MB內(nèi)存

等了1分30秒都沒出結(jié)果,而且線程卡在MySQL傳輸數(shù)據(jù)上:

at java.io.FilterInputStream.read(FilterInputStream.java:133)
    at com.mysql.cj.protocol.FullReadInputStream.readFully(FullReadInputStream.java:64)
    at com.mysql.cj.protocol.a.SimplePacketReader.readMessageLocal(SimplePacketReader.java:137)
    at com.mysql.cj.protocol.a.SimplePacketReader.readMessage(SimplePacketReader.java:102)

3.1. 使用游標(biāo)Cursor,配置 FORWARD_ONLY

@Select("select * from test.test_table union all " +
        "select * from test.test_table union all " +
        "select * from test.test_table union all " +
        "select * from test.test_table union all " +
        "select * from test.test_table union all " +
        "select * from test.test_table union all " +
        "select * from test.test_table union all " +
        "select * from test.test_table union all " +
        "select * from test.test_table union all " +
        "select * from test.test_table")
@Options(resultSetType = ResultSetType.FORWARD_ONLY)
Cursor<Person> selectAll();

  • 查詢過程耗時:6313ms
  • GC:22次
  • 占用內(nèi)存:454MB

加了這個參數(shù)不如2.1不配置參數(shù)的情況。

3.2. 使用游標(biāo)Cursor,配置 FORWARD_ONLY,限制200MB內(nèi)存

仍然內(nèi)存溢出:

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: java.sql.SQLException: GC overhead limit exceeded

以上測試說明 @Options(resultSetType = ResultSetType.FORWARD_ONLY) 配置沒用。

從 MyBatis 源碼來看,就沒有相關(guān)的代碼,不起作用是正常的,但是奇怪的是,網(wǎng)上搜的大量文章都是加的這個配置。

接下來看看真正有用的配置。

4.1. 使用游標(biāo)Cursor,配置 @Options(fetchSize = Integer.MIN_VALUE)

  • 查詢過程耗時:4735ms
  • GC:12次
  • 占用內(nèi)存:206MB

這種情況比前面的都好,而且GC只有12次,內(nèi)存比3.1少了一半。

4.2. 使用游標(biāo)Cursor,配置 @Options(fetchSize = Integer.MIN_VALUE),內(nèi)存限制50MB

  • 查詢過程耗時:4676ms
  • GC:142次
  • 占用內(nèi)存:16MB

16MB內(nèi)存就能處理百萬數(shù)據(jù),但是GC增加了,GC耗時231ms。

4.3. 使用游標(biāo)Cursor,配置 @Options(fetchSize = Integer.MIN_VALUE),內(nèi)存限制10MB

  • 查詢過程耗時:38715ms
  • GC:1894次
  • 占用內(nèi)存:7.8MB
  • 16MB內(nèi)存就能處理百萬數(shù)據(jù),但是GC增加了,GC耗時34s。

程序一共運行了39秒,其中34秒是GC時間,吞吐量只有13%,太低了,限制50MB時使用了16MB,增加一次限制20MB的測試。

4.4. 使用游標(biāo)Cursor,配置 @Options(fetchSize = Integer.MIN_VALUE),內(nèi)存限制20MB

  • 查詢過程耗時:4880ms
  • GC:366次
  • 占用內(nèi)存:7.8MB
  • 16MB內(nèi)存就能處理百萬數(shù)據(jù),但是GC增加了,GC耗時514ms,吞吐量90%。

正確使用MyBatis游標(biāo)

從上面結(jié)果來看,真正有效的是 @Options(fetchSize = Integer.MIN_VALUE) 配置。

如果追查到JDBC層,會在 mysql 的 jdbc 驅(qū)動StatementImpl類中發(fā)現(xiàn)下面的方法:

/**
 * We only stream result sets when they are forward-only, read-only, and the
 * fetch size has been set to Integer.MIN_VALUE
 * 
 * @return true if this result set should be streamed row at-a-time, rather
 *         than read all at once.
 */
protected boolean createStreamingResultSet() {
    return ((this.query.getResultType() == Type.FORWARD_ONLY) && (this.resultSetConcurrency == java.sql.ResultSet.CONCUR_READ_ONLY)
            && (this.query.getResultFetchSize() == Integer.MIN_VALUE));
}

我們加的注解中,fetchSize條件滿足了,另外兩個在何時設(shè)置的呢?

在AbstractQuery中,存在下面的默認(rèn)值:

protected Resultset.Type resultSetType = Type.FORWARD_ONLY;

在 ConnectionImpl 中的下面方法也有默認(rèn)參數(shù):

@Override
public java.sql.PreparedStatement prepareStatement(String sql) throws SQLException {
    return prepareStatement(sql, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY);
}

所以在 MySQL 中,啟用流式傳輸就需要 @Options(fetchSize = Integer.MIN_VALUE) 配置。

當(dāng)考慮到更多類型的數(shù)據(jù)庫時,fetchSize 一般都有不同大小的默認(rèn)值,像 MySQL 這樣直接用 Integer.MIN_VALUE 的不多見,Type.FORWARD_ONLY 也是一些數(shù)據(jù)庫的默認(rèn)值,為了保險可以設(shè)置上,就目前的游標(biāo)功能來看,針對不同的數(shù)據(jù)庫要做對應(yīng)的測試才能找到合適的參數(shù)配置。

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java?中向?Arraylist?添加對象的示例代碼

    Java?中向?Arraylist?添加對象的示例代碼

    本文介紹了如何在 Java 中向 ArrayList 添加對象,并提供了示例和注意事項,通過掌握這些知識,讀者可以在自己的 Java 項目中有效地使用 ArrayList 來存儲和操作對象,需要的朋友可以參考下
    2023-11-11
  • java隨機(jī)生成一個名字和對應(yīng)拼音的方法

    java隨機(jī)生成一個名字和對應(yīng)拼音的方法

    這篇文章主要介紹了java隨機(jī)生成一個名字和對應(yīng)拼音的方法,涉及java針對數(shù)組及隨機(jī)數(shù)操作的相關(guān)技巧,需要的朋友可以參考下
    2015-07-07
  • Spring cloud config 配置文件加密方式

    Spring cloud config 配置文件加密方式

    這篇文章給大家介紹了Spring cloud config 配置文件加密方式,非常不錯,具有一定的參考借鑒價值,感興趣的朋友跟隨腳步之家小編一起學(xué)習(xí)吧
    2018-05-05
  • SpringBoot整合RocketMQ實現(xiàn)消息發(fā)送和接收的詳細(xì)步驟

    SpringBoot整合RocketMQ實現(xiàn)消息發(fā)送和接收的詳細(xì)步驟

    這篇文章主要介紹了SpringBoot整合RocketMQ實現(xiàn)消息發(fā)送和接收功能,我們使用主流的SpringBoot框架整合RocketMQ來講解,使用方便快捷,本文分步驟給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2021-08-08
  • MyBatis?的?XML?配置文件和緩存使用步驟

    MyBatis?的?XML?配置文件和緩存使用步驟

    MyBatis?包含一個非常強(qiáng)大的查詢緩存特性,它可以非常方便地配置和定制,這篇文章主要介紹了MyBatis的XML配置文件和緩存,需要的朋友可以參考下
    2022-01-01
  • MyBatisPlus 暫時分頁失效原因及問題解決

    MyBatisPlus 暫時分頁失效原因及問題解決

    MybatisPlus升級后分頁失效,因新版本PaginationInnerInterceptor中新增maxLimit限制,下面就來介紹MyBatisPlus 暫時分頁失效原因及問題解決,感興趣的可以了解一下
    2025-07-07
  • 使用IDEA創(chuàng)建servlet?JavaWeb?應(yīng)用及使用Tomcat本地部署的實現(xiàn)

    使用IDEA創(chuàng)建servlet?JavaWeb?應(yīng)用及使用Tomcat本地部署的實現(xiàn)

    本文主要介紹了使用IDEA創(chuàng)建servlet?JavaWeb?應(yīng)用及使用Tomcat本地部署
    2022-01-01
  • LinkedHashMap如何保證有序問題

    LinkedHashMap如何保證有序問題

    這篇文章主要介紹了LinkedHashMap如何保證有序問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • java讀取資源路徑的幾種實現(xiàn)方式

    java讀取資源路徑的幾種實現(xiàn)方式

    文章總結(jié)了Java讀取資源路徑的幾種方式,并指出了在JUnit測試文件和普通類中讀取資源路徑的區(qū)別,普通類中讀取資源路徑時,只返回主目錄,而JUnit測試文件中可以精確到所在模塊
    2025-02-02
  • Java中的遠(yuǎn)程方法調(diào)用之RPC使用

    Java中的遠(yuǎn)程方法調(diào)用之RPC使用

    本文介紹了RPC的基本概念、核心思想及其在分布式系統(tǒng)中的作用,重點講解了Java中的三種RPC實現(xiàn)方式:RMI、Hessian和Dubbo,分別從適用場景、核心特點等方面做了對比對比較,并并提供了其基本實現(xiàn)代碼示例,總結(jié)了各框架的優(yōu)缺點,為實際開發(fā)中選擇合適的技術(shù)棧提供了參考
    2026-05-05

最新評論

阜城县| 如东县| 文水县| 深州市| 焉耆| 娄烦县| 宝清县| 英山县| 云龙县| 阳西县| 永寿县| 日土县| 腾冲县| 衡南县| 穆棱市| 科尔| 大港区| 巴马| 阿鲁科尔沁旗| 汉沽区| 桐梓县| 四平市| 尼木县| 寿宁县| 太谷县| 邵阳市| 师宗县| 泰兴市| 明星| 郸城县| 类乌齐县| 如东县| 汉川市| 龙陵县| 宁强县| 岳西县| 阳江市| 错那县| 柞水县| 瓮安县| 陈巴尔虎旗|