пятница, 21 сентября 2012 г.

PL/SQL BULK insert & JDBC batchExecute

Ко мне обратились разработчи с вопросом об эффективности использования executeBatch при использовании JDBC в сравнении с Oracle PL/SQL BULK (forall) insert.

В результате родился небольшой код на java, который производит вызов PL/SQL блока с реализованной BULK вставкой, а так же проделывает такой же объем работы посредством Java JDBC вызовов, но уже с использованием executeBatch().

PL/SQL код BULK операции:

declare
  type t_tbl is table of batchtest.n%type;
  v_tbl t_tbl := t_tbl();
begin
  for i in 1..1000 loop
    v_tbl.extend;
    v_tbl(i) := i;
  end loop;
  forall n in 1..v_tbl.count insert into batchtest /*PLSQL*/ values(1, v_tbl(n));
end;


Java код Batch вызова:


PreparedStatement psttm2 = conn.prepareStatement("insert into batchtest /*JDBC*/ values(2, ?)");
for (int n=1; n<=1000;n++) {
               psttm2.setInt(1, n);
               psttm2.addBatch();
}
psttm2.executeBatch();
conn.commit();

Результат, полученный из анализа Trace файла:

Rank:2(28.6%) Self:0.946s Recursive:0.021s Invoker:52 Definer:52 Depth:1

INSERT INTO BATCHTEST VALUES(1, :B1 )

SQL Self - Time, Totals,
Waits, Binds and Row Source Plan
Call
Response Time
Accounted-for
Elapsed
Time
CPU Time
Non-Idle
Wait Time
Elapsed Time
Unaccounted-for
Idle
Wait Time
Parse:
0.001
0.001
0.001
0.000
0.000
0.000
Execute:
0.945
0.945
0.381
0.565
0.000
0.000
Fetch:
0.000
0.000
0.000
0.000
0.000
0.000
Total:
0.946
0.946
0.382
0.565
-0.001
0.000

Call
Call
Count
OS
Buffer Gets
(disk)
BG Consistent
Read Mode
(query)
BG Current
Mode
(current)
Rows
Processed
or Returned
Library
Cache
Misses
Times
Waited
Non-Idle
Times
Waited
Idle
Parse:
2
0
0
0
0
1
0
0
Execute:
200
17
799
4793
200000
1
62
0
Fetch:
0
0
0
0
0
0
0
0
Total:
202
17
799
4793
200000
2
62
0


Rank:3(22.5%) Self:0.742s Recursive:0.000s Invoker:52 Definer:52 Depth:0

insert into batchtest /*JDBC*/ values(2, :1 )

SQL Self - Time, Totals, Waits, Binds and Row Source Plan

Call
Response Time
Accounted-for
Elapsed
Time
CPU Time
Non-Idle
Wait Time
Elapsed Time
Unaccounted-for
Idle
Wait Time
Parse:
0.001
0.001
0.001
0.000
0.000
0.000
Execute:
0.742
0.574
0.297
1.807
-1.530
0.168
Fetch:
0.000
0.000
0.000
0.000
0.000
0.000
Total:
0.742
0.575
0.298
1.807
-1.531
0.168

Call
Call
Count
OS
Buffer Gets
(disk)
BG Consistent
Read Mode
(query)
BG Current
Mode
(current)
Rows
Processed
or Returned
Library
Cache
Misses
Times
Waited
Non-Idle
Times
Waited
Idle
Parse:
1
0
0
0
0
1
0
0
Execute:
200
12
733
4559
200000
1
423
199
Fetch:
0
0
0
0
0
0
0
0
Total:
201
12
733
4559
200000
2
423
199



Исходя из результатов анализа trace файла можно заключить, что
1) Oracle PL/SQL Bulk и Java JDBC Batch имеют одинаковую картину вызова
2) Оба запроса создают близкую нагрузку как по характеру так и по объему
3) *** При многократном запуске усредненная производительность обоих запросов расходилась менее, чем на 1%.

P.S.> Поскольку на большинстве тестов JDBC Batch показывал - как минимум - не меньшую производительность, то тесты с запуском Java из базы, дабы исключить влияние задержек SQL*Net на доставку объема batch, не проводились.


Анализ статистики был проведён посредством Oracle Trace Analyzer.
Ниже привожу java код:

oracleBatchTest.java


import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import oracle.jdbc.pool.OracleDataSource;

public class oracleBatchTest {
   
    public static void main(String argv[]) throws SQLException {
        OracleDataSource ods = new OracleDataSource();
        ods.setURL("jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=clu-host-02.net.billing.ru)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=dvlp11.net.billing.ru)(INSTANCE_NAME=dvlp112)))");
        ods.setUser("ashindarev");
        ods.setPassword("********");
        Connection conn = ods.getConnection();
        Statement sttm = conn.createStatement();
        sttm.execute("begin\n"+
                     "  for rec in (\n"+
                     "    select count(1) as cnt from user_tables where table_name = 'BATCHTEST'\n"+
                     "  ) loop\n"+
                     "    if rec.cnt = 0 then\n"+
                     "      execute immediate 'create table batchtest(id integer, n number) storage(initial 1M next 1M pctincrease 0 minextents 4)';\n"+
                     "    end if;\n"+
                     "  end loop;\n"+
                     "  execute immediate 'truncate table batchtest reuse storage';\n"+
                     "end;");
        sttm.execute("ALTER SESSION SET EVENTS '10046 trace name context forever, level 8'");
        PreparedStatement psttm1 = conn.prepareStatement(
                                       "declare\n"+
                                       "  type t_tbl is table of batchtest.n%type;\n"+
                                       "  v_tbl t_tbl := t_tbl();\n"+
                                       "begin\n"+
                                       "  for i in 1..1000 loop\n"+
                                       "    v_tbl.extend;\n"+
                                       "    v_tbl(i) := i;\n"+
                                       "  end loop;\n"+
                                       "  forall n in 1..v_tbl.count insert into batchtest /*PLSQL*/ values(1, v_tbl(n));\n"+
                                       "end;"
                                   );
        PreparedStatement psttm2 = conn.prepareStatement("insert into batchtest /*JDBC*/ values(2, ?)");
        for (int i=0; i<100; i++) {
            psttm1.execute();
            conn.commit();
            for (int n=1; n<=1000;n++) {
                psttm2.setInt(1, n);
                psttm2.addBatch();
            }
            psttm2.executeBatch();
            conn.commit();
            psttm2.clearBatch();
        }
        for (int i=0; i<100; i++) {
            for (int n=1; n<=1000;n++) {
                psttm2.setInt(1, n);
                psttm2.addBatch();
            }
            psttm2.executeBatch();
            psttm2.clearBatch();
            conn.commit();
            psttm1.execute();
            conn.commit();
        }
        sttm.execute("ALTER SESSION SET EVENTS '10046 trace name context off'");
        sttm.execute("drop table BATCHTEST purge");
        conn.close();
    }
   
}

Комментариев нет:

Отправить комментарий