如何在 pl/sql oracle 中在 5 分钟内插入 100 万条数据?

Posted

技术标签:

【中文标题】如何在 pl/sql oracle 中在 5 分钟内插入 100 万条数据?【英文标题】:How to insert 1 million data less 5 minute in pl/sql oracle? 【发布时间】:2019-01-04 06:18:00 【问题描述】:

需要在 5 分钟内插入大量记录。这是我尝试过的pl/SQL,

procedure insert_student(name_      in varchar2,
                         address_   in varchar2,
                         phone_     in varchar2,
                         class_     in varchar2) is                                  

        begin

          insert into student.student_scholarship(name, address,
          phone, class, date)
          values (name_, address_, phone_, class_, sysdate)
          );
        commit;                
end insert_student; 

【问题讨论】:

你试过了吗?你有什么基准吗? 对于大型数据集,它首选使用 INSERT AS SELECT 或 FORALL。 (您可以使用 APPEND 提示提示 INSERT) 指定您的数据源,因为如果您不这样做,您可能会得到很多不同且不相关的答案。如果您想获得性能,您需要的是某种 BULK 操作。基本上,PL/SQL 将数据存储在 RAM 中,并将其用作批量插入行的快速方法。看看它,自己尝试一下,然后在这里举个例子。 我的朋友对这些东西很好' sqlload docs.oracle.com/cd/B19306_01/server.102/b14215/ldr_concepts.htm 要插入的百万行是否来自另一个表、一个平面文件、另一个数据库?适合您的答案将取决于数据源。 5 分钟一百万应该不会太具有挑战性。 【参考方案1】:

对于下面的模拟,我们让 A 生成了一个 STUDENT 表,其中包含 1,000,000 行,填充了随机字符串。由于您没有告诉我们从哪里加载数据,我们已B将数据导出/卸载到 CSV 文件,C 通过 EXTERNAL 表使用数据,我们然后利用各种 INSERT 技术。 (全部使用 Oracle 12c,“Developer Days”VM)

A“源表”

create table student ( name, address, phone, sclass )
as
select 
  dbms_random.string( 'x', 25 )
, dbms_random.string( 'x', 40 )
, dbms_random.string( 'x', 20 )
, dbms_random.string( 'x', 5 )
from dual
connect by level <= 1000000 ;
-- Elapsed: 00:03:25.032

-- quick check
select count(*) from student ;

  COUNT(*)
----------
   1000000

B 将 1,000,000 行写入 CSV 文件

set term off
set feed off
set sqlformat csv
spool /home/oracle/data_out/out.csv
select /*+ parallel */* from student ;
spool off

C 外部表

create table external_ ( 
  name    varchar2( 4000 )
, address varchar2( 4000 )
, phone   varchar2( 4000 )
, sclass  varchar2( 4000 )
) 
organization external (
  type oracle_loader 
  default directory external_tables
  access parameters 
  ( 
     records field names all files
     fields CSV with embedded record terminators
  ) 
  location 
  (
    'out.csv'
  ) 
)
/
-- quick check
SQL> select count(*) from external_ ;

  COUNT(*)
----------
   1000000

“目的地”表

create table scholarship (
  name    varchar2( 25 )
, address varchar2( 40 )
, phone   varchar2( 20 )
, sclass   varchar2( 5 )
, sdate   date default sysdate
);

当使用纯 SQL 插入 1,000,000 行时,我们得到以下时间(测试运行 3 次,SCHOLARSHIP 表在测试之间被 DROPped)。

-- 1  SQL: INSERT ... SELECT ...
insert into scholarship ( name, address, phone, sclass ) 
select name, address, phone, sclass from external_ ;

-- 1,000,000 rows inserted.
-- Elapsed: 00:00:02.607
-- Elapsed: 00:00:02.300
-- Elapsed: 00:00:02.473

可能最糟糕的选择是:使用 PL/SQL 和 CURSOR FOR LOOP(测试运行 3 次,SCHOLARSHIP 在两次测试之间中断)。

--2  PL/SQL: use a cursor for loop ("slow by slow")
begin
  for rec_ in ( select * from external_ )
  loop
    insert into scholarship ( name, address, phone, sclass )
    values ( rec_.name, rec_.address, rec_.phone, rec_.sclass ) ;
  end loop ;
  commit ;
end ;
/

-- PL/SQL procedure successfully completed.
-- Elapsed: 00:00:24.777
-- Elapsed: 00:00:22.700
-- Elapsed: 00:00:24.291

更好一点:使用 PL/SQL 的批量操作(同样使用“经过时间”进行 3 次测试运行)。

--3  PL/SQL: use BULK COLLECT and FORALL (no need to re-compile in between tests)
create or replace procedure insert_students is
  type student_t is table of external_%rowtype index by pls_integer ;
  lstudents student_t ;
begin
  select * bulk collect into lstudents from external_  ;
  forall i in 1.. lstudents.count
    insert into scholarship ( name, address, phone, sclass )
    values ( lstudents( i ).name, lstudents( i ).address, lstudents( i ).phone, lstudents( i ).sclass  );
end ;
/

begin
  insert_students ;
  commit ;
end ;
/

-- PL/SQL procedure successfully completed.
-- Elapsed: 00:00:08.706
-- Elapsed: 00:00:06.762
-- Elapsed: 00:00:04.989

正如许多人会告诉您的那样:尽可能使用 SQL(仅)。现在,您可能会看到您的初始方法 - 使用带有参数的过程,并且一次只执行一次 INSERT - 可能不是解决问题的最佳技术。

【讨论】:

【参考方案2】:

使用FORALL 语句。它比使用FOR 循环逐一保存记录要快得多,因为它不会在每次 PL/SQL 处理器通过 SQL 语句时更改 PL/SQL 和 SQL 之间的上下文。

CREATE TABLE students (
    id NUMBER(19,0),
    address VARCHAR2(300)
);
/

CREATE OR REPLACE PACKAGE pack AS
TYPE t_students IS TABLE OF students%ROWTYPE INDEX BY BINARY_INTEGER;

PROCEDURE insert_students( l_students IN t_students);

END pack;
/

CREATE OR REPLACE PACKAGE BODY pack AS

PROCEDURE insert_students( l_students IN t_students) AS
    BEGIN
      FORALL i IN 1..l_students.COUNT
      INSERT INTO students VALUES (l_students(i).id, l_students(i).address);
    END;
END pack;
/

【讨论】:

FORALL 比单个调用快,但可能不比纯 SQL insert into ... select * from ... 快​​。 OP 没有提供有关其输入数据来源的详细信息,因此我们没有理由认为他们根本需要任何 PL/SQL。

以上是关于如何在 pl/sql oracle 中在 5 分钟内插入 100 万条数据?的主要内容,如果未能解决你的问题,请参考以下文章

oracle pl/sql如何定义变量

如何将xls的资料导入到oracle数据库中

如何计算oracle pl/sql中数字的位数?

如何使用 Oracle (PL/SQL) 动态 sql 将数据查询到 %rowtype 变量中

oracle pl/sql 数组

oracle创建存储过程时,提示错误是:错误(5,18): PL/SQL: ORA-00947: 没有足够的值?代码如下: