欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java實(shí)現(xiàn)JDBC批量插入原理詳解

 更新時(shí)間:2023年03月11日 08:48:03   作者:小豹子加油  
在JDBC中,executeBatch這個(gè)方法可以將多條dml語句批量執(zhí)行,效率比單條執(zhí)行executeUpdate高很多,這是什么原理呢?在mysql和oracle中又是如何實(shí)現(xiàn)批量執(zhí)行的呢?本文將給大家介紹這背后的原理

一、說明

在JDBC中,executeBatch這個(gè)方法可以將多條dml語句批量執(zhí)行,效率比單條執(zhí)行executeUpdate高很多,這是什么原理呢?在mysql和oracle中又是如何實(shí)現(xiàn)批量執(zhí)行的呢?本文將給大家介紹這背后的原理。

二、實(shí)驗(yàn)介紹

本實(shí)驗(yàn)將通過以下三步進(jìn)行

a. 記錄jdbc在mysql中批量執(zhí)行和單條執(zhí)行的耗時(shí)

b. 記錄jdbc在oracle中批量執(zhí)行和單條執(zhí)行的耗時(shí)

c. 記錄oracle plsql批量執(zhí)行和單條執(zhí)行的耗時(shí)

相關(guān)java和數(shù)據(jù)庫版本如下:Java17,Mysql8,Oracle11G

三、正式實(shí)驗(yàn)

在mysql和oracle中分別創(chuàng)建一張表

create table t (  -- mysql中創(chuàng)建表的語句
    id    int,
    name1 varchar(100),
    name2 varchar(100),
    name3 varchar(100),
    name4 varchar(100)
);
create table t (  -- oracle中創(chuàng)建表的語句
    id    number,
    name1 varchar2(100),
    name2 varchar2(100),
    name3 varchar2(100),
    name4 varchar2(100)
);

在實(shí)驗(yàn)前需要打開數(shù)據(jù)庫的審計(jì)

mysql開啟審計(jì):

set global general_log = 1;

oracle開啟審計(jì):

alter system set audit_trail=db, extended;  
audit insert table by scott;  -- 實(shí)驗(yàn)采用scott用戶批量執(zhí)行insert的方式

java代碼如下:

import java.sql.*;

public class JdbcBatchTest {

    /**
     * @param dbType 數(shù)據(jù)庫類型,oracle或mysql
     * @param totalCnt 插入的總行數(shù)
     * @param batchCnt 每批次插入的行數(shù),0表示單條插入
     */
    public static void exec(String dbType, int totalCnt, int batchCnt) throws SQLException, ClassNotFoundException {
        String user = "scott";
        String password = "xxxx";
        String driver;
        String url;
        if (dbType.equals("mysql")) {
            driver = "com.mysql.cj.jdbc.Driver";
            url = "jdbc:mysql://ip/hello?useServerPrepStmts=true&rewriteBatchedStatements=true";
        } else {
            driver = "oracle.jdbc.OracleDriver";
            url = "jdbc:oracle:thin:@ip:orcl";
        }

        long l1 = System.currentTimeMillis();
        Class.forName(driver);
        Connection connection = DriverManager.getConnection(url, user, password);
        connection.setAutoCommit(false);
        String sql = "insert into t values (?, ?, ?, ?, ?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        for (int i = 1; i <= totalCnt; i++) {
            preparedStatement.setInt(1, i);
            preparedStatement.setString(2, "red" + i);
            preparedStatement.setString(3, "yel" + i);
            preparedStatement.setString(4, "bal" + i);
            preparedStatement.setString(5, "pin" + i);

            if (batchCnt > 0) {
                // 批量執(zhí)行
                preparedStatement.addBatch();
                if (i % batchCnt == 0) {
                    preparedStatement.executeBatch();
                } else if (i == totalCnt) {
                    preparedStatement.executeBatch();
                }
            } else {
                // 單條執(zhí)行
                preparedStatement.executeUpdate();
            }
        }
        connection.commit();
        connection.close();
        long l2 = System.currentTimeMillis();
        System.out.println("總條數(shù):" + totalCnt + (batchCnt>0? (",每批插入:"+batchCnt) : ",單條插入") + ",一共耗時(shí):"+ (l2-l1) + " 毫秒");
    }

    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        exec("mysql", 10000, 50);
    }
}

代碼中幾個(gè)注意的點(diǎn),

  • mysql的url需要加入useServerPrepStmts=true&rewriteBatchedStatements=true參數(shù)。
  • batchCnt表示每次批量執(zhí)行的sql條數(shù),0表示單條執(zhí)行。

首先測試mysql

exec("mysql", 10000, batchCnt);

代入不同的batchCnt值看執(zhí)行時(shí)長

batchCnt=50 總條數(shù):10000,每批插入:50,一共耗時(shí):4369 毫秒
batchCnt=100 總條數(shù):10000,每批插入:100,一共耗時(shí):2598 毫秒
batchCnt=200 總條數(shù):10000,每批插入:200,一共耗時(shí):2211 毫秒
batchCnt=1000 總條數(shù):10000,每批插入:1000,一共耗時(shí):2099 毫秒
batchCnt=10000 總條數(shù):10000,每批插入:10000,一共耗時(shí):2418 毫秒
batchCnt=0 總條數(shù):10000,單條插入,一共耗時(shí):59620 毫秒

查看general log

batchCnt=5

batchCnt=0

可以得出幾個(gè)結(jié)論:

  • 批量執(zhí)行的效率相比單條執(zhí)行大大提升。
  • mysql的批量執(zhí)行其實(shí)是改寫了sql,將多條insert合并成了insert xx values(),()...的方式去執(zhí)行。
  • 將batchCnt由50改到100的時(shí)候,時(shí)間基本上縮短了一半,但是再擴(kuò)大這個(gè)值的時(shí)候,時(shí)間縮短并不明顯,執(zhí)行的時(shí)間甚至還會升高。

分析原因:

當(dāng)執(zhí)行一條sql語句的時(shí)候,客戶端發(fā)送sql文本到數(shù)據(jù)庫服務(wù)器,數(shù)據(jù)庫執(zhí)行sql再將結(jié)果返回給客戶端??偤臅r(shí) = 數(shù)據(jù)庫執(zhí)行時(shí)間 + 網(wǎng)絡(luò)傳輸時(shí)間。使用批量執(zhí)行減少往返的次數(shù),即降低了網(wǎng)絡(luò)傳輸時(shí)間,總時(shí)間因此降低。但是當(dāng)batchCnt變大,網(wǎng)絡(luò)傳輸時(shí)間并不是最主要耗時(shí)的時(shí)候,總時(shí)間降低就不會那么明顯。特別是當(dāng)batchCnt=10000,即一次性把1萬條語句全部執(zhí)行完,時(shí)間反而變多了,這可能是由于程序和數(shù)據(jù)庫在準(zhǔn)備這些入?yún)r(shí)需要申請更大的內(nèi)存,所以耗時(shí)更多(我猜的)。

再來說一句,batchCnt這個(gè)值是不是能無限大呢,假設(shè)我需要插入的是1億條,那么我能一次性批量插入1億條嗎?當(dāng)然不行,我們不考慮undo的空間問題,首先你電腦就沒有這么大的內(nèi)存一次性把這1億條sql的入?yún)⑷勘4嫦聛恚浯蝝ysql還有個(gè)參數(shù)max_allowed_packet限制單條語句的長度,最大為1G字節(jié)。當(dāng)語句過長的時(shí)候就會報(bào)"Packet for query is too large (1,773,901 > 1,599,488). You can change this value on the server by setting the 'max_allowed_packet' variable"。

接下來測試oracle

exec("oracle", 10000, batchCnt);

代入不同的batchCnt值看執(zhí)行時(shí)長

batchCnt=50 總條數(shù):10000,每批插入:50,一共耗時(shí):2055 毫秒
batchCnt=100 總條數(shù):10000,每批插入:100,一共耗時(shí):1324 毫秒
batchCnt=200 總條數(shù):10000,每批插入:200,一共耗時(shí):856 毫秒
batchCnt=1000 總條數(shù):10000,每批插入:1000,一共耗時(shí):785 毫秒
batchCnt=10000 總條數(shù):10000,每批插入:10000,一共耗時(shí):804 毫秒
batchCnt=0 總條數(shù):10000,單條插入,一共耗時(shí):60830 毫秒

可以看到oracle中執(zhí)行的效果跟mysql中基本一致,批量執(zhí)行的效率相比單條執(zhí)行都大大提升。問題就來了,oracle中并沒有這種insert xx values(),()..語法呀,那它是怎么做到批量執(zhí)行的呢?

查看當(dāng)執(zhí)行batchCnt=50的審計(jì)視圖dba_audit_trail

從審計(jì)的結(jié)果中可以看到,batchCnt=50的時(shí)候,審計(jì)記錄只有200條(扣除登入和登出),也就是sql只執(zhí)行了200次。sql_text沒有發(fā)生改寫,仍然是"insert into t values (:1 , :2 , :3 , :4 , :5 )",而且sql_bind只記錄了批量執(zhí)行的最后一個(gè)參數(shù),即50的倍數(shù)。從awr報(bào)告中也能看出的確是只執(zhí)行了200次(限于篇幅,awr截圖省略)。那么oracle是怎么做到只執(zhí)行200次但插入1萬條記錄的呢?我們來看看oracle中使用存儲過程的批量插入。

四、存儲過程

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

首先將t表清空 truncate table t;

用java往t表灌10萬數(shù)據(jù) exec("oracle", 100000, 1000);

創(chuàng)建t1表 create table t1 as select * from t where 1 = 0;

以下兩個(gè)procudure的目的相同,都是將t表的數(shù)據(jù)灌到t1表中。nobatch是單次執(zhí)行,usebatch是批量執(zhí)行。

create or replace procedure nobatch is
begin
  for x in (select * from t)
  loop
    insert into t1 (id, name1, name2, name3, name4)
    values (x.id, x.name1, x.name2, x.name3, x.name4);
  end loop;
  commit;
end nobatch;
/
create or replace procedure usebatch (p_array_size in pls_integer)
is
  type array is table of t%rowtype;
  l_data array;
  cursor c is select * from t;
begin
  open c;
  loop
    fetch c bulk collect into l_data limit p_array_size;
    forall i in 1..l_data.count insert into t1 values l_data(i);
    exit when c%notfound;
  end loop;
  commit;
  close c;
end usebatch;
/

執(zhí)行上述存儲過程

SQL> exec nobatch;  
Elapsed: 00:00:32.92

SQL> exec usebatch(50);
Elapsed: 00:00:00.77

SQL> exec usebatch(100);
Elapsed: 00:00:00.47

SQL> exec usebatch(1000);
Elapsed: 00:00:00.19

SQL> exec usebatch(100000);
Elapsed: 00:00:00.26

存儲過程批量執(zhí)行效率也遠(yuǎn)遠(yuǎn)高于單條執(zhí)行。查看usebatch(50)執(zhí)行時(shí)的審計(jì)日志,sql_bind也只記錄了批量執(zhí)行的最后一個(gè)參數(shù),即50的倍數(shù)。跟前面jdbc使用executeBatch批量執(zhí)行時(shí)的記錄內(nèi)容一樣。由此可知jdbc的executeBatch跟存儲過程的批量執(zhí)行應(yīng)該是采用的同樣的方法

存儲過程的這個(gè)關(guān)鍵點(diǎn)就是forall。查閱相關(guān)文檔。

The FORALL statement runs one DML statement multiple times, with different values in the VALUES and WHERE clauses.
The different values come from existing, populated collections or host arrays. The FORALL statement is usually much faster than an equivalent FOR LOOP statement.
The FORALL syntax allows us to bind the contents of a collection to a single DML statement, allowing the DML to be run for each row in the collection without requiring a context switch each time.

翻譯過來就是forall很快,原因就是不需要每次執(zhí)行的時(shí)候等待參數(shù)。

五、總結(jié)

  • mysql的批量執(zhí)行就是改寫sql。
  • oracle的批量執(zhí)行就是用的forall。
  • 選擇一個(gè)合適批量值。

到此這篇關(guān)于Java實(shí)現(xiàn)JDBC批量插入原理詳解的文章就介紹到這了,更多相關(guān)Java JDBC批量插入內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 從零開始學(xué)springboot整合feign跨服務(wù)調(diào)用的方法

    從零開始學(xué)springboot整合feign跨服務(wù)調(diào)用的方法

    這篇文章主要介紹了從零開始學(xué)springboot整合feign跨服務(wù)調(diào)用的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-03-03
  • 詳解Spring與Mybatis的整合方法(基于Eclipse的搭建)

    詳解Spring與Mybatis的整合方法(基于Eclipse的搭建)

    這篇文章主要介紹了Spring與Mybatis的整合方法(基于Eclipse的搭建),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-10-10
  • Springboot+Bootstrap實(shí)現(xiàn)增刪改查實(shí)戰(zhàn)

    Springboot+Bootstrap實(shí)現(xiàn)增刪改查實(shí)戰(zhàn)

    這篇文章主要介紹了Springboot+Bootstrap實(shí)現(xiàn)增刪改查實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • 使用springMVC所需要的pom配置

    使用springMVC所需要的pom配置

    這篇文章主要介紹了使用springMVC所需要的pom配置,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Java基于循環(huán)遞歸回溯實(shí)現(xiàn)八皇后問題算法示例

    Java基于循環(huán)遞歸回溯實(shí)現(xiàn)八皇后問題算法示例

    這篇文章主要介紹了Java基于循環(huán)遞歸回溯實(shí)現(xiàn)八皇后問題算法,結(jié)合具體實(shí)例形式分析了java的遍歷、遞歸、回溯等算法實(shí)現(xiàn)八皇后問題的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下
    2017-06-06
  • Java求10到100000之間的水仙花數(shù)算法示例

    Java求10到100000之間的水仙花數(shù)算法示例

    這篇文章主要介紹了Java求10到100000之間的水仙花數(shù)算法,結(jié)合實(shí)例形式分析了水仙花數(shù)的概念及相應(yīng)的java算法實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2017-10-10
  • Java多線程基本概念以及避坑指南

    Java多線程基本概念以及避坑指南

    多線程之于進(jìn)程的理解,可以類比多進(jìn)程之于操作系統(tǒng),多線程指在單個(gè)程序中可以同時(shí)運(yùn)行多個(gè)不同的線程執(zhí)行不同的任務(wù),這篇文章主要給大家介紹了關(guān)于Java多線程基本概念以及避坑指南的相關(guān)資料,需要的朋友可以參考下
    2021-09-09
  • Java Hibernate中使用HQL語句進(jìn)行數(shù)據(jù)庫查詢的要點(diǎn)解析

    Java Hibernate中使用HQL語句進(jìn)行數(shù)據(jù)庫查詢的要點(diǎn)解析

    HQL是Hibernate框架中提供的關(guān)系型數(shù)據(jù)庫操作腳本,當(dāng)然我們也可以使用原生的SQL語句,這里我們來看一下在Java Hibernate中使用HQL語句進(jìn)行數(shù)據(jù)庫查詢的要點(diǎn)解析:
    2016-06-06
  • mybatis查詢SqlServer慢問題及解決

    mybatis查詢SqlServer慢問題及解決

    這篇文章主要介紹了mybatis查詢SqlServer慢問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • Java下SpringBoot創(chuàng)建定時(shí)任務(wù)詳解

    Java下SpringBoot創(chuàng)建定時(shí)任務(wù)詳解

    這篇文章主要介紹了Java下SpringBoot創(chuàng)建定時(shí)任務(wù)詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07

最新評論