我可以向 Oracle 发送“批量”插入吗?

Posted

技术标签:

【中文标题】我可以向 Oracle 发送“批量”插入吗?【英文标题】:Can I send "batched" INSERTs to Oracle? 【发布时间】:2009-02-11 13:18:12 【问题描述】:

场景:

我每天将一些数据加载到本地 mysql 数据库中,大约 200 万行; 我必须(不得不 - 这是一个审计/监管的事情)移动到“正确”管理的服务器,目前看起来是 Oracle 10g; 服务器在不同的国家:网络往返电流需要 60-70 毫秒; 输入是非规范化形式的 CSV 文件:我在加载之前对数据进行了规范化,每行通常会在最多 4 个表中产生 3-8 个 INSERT; 加载脚本目前在 Ruby 中实现,使用 ActiveRecord 和 fastercsv。我已经尝试了 ar-extensions gem,但它假设 MySQL 样式的多值子句的想法会起作用。没有。

编辑:已经非常有用的答案 - 谢谢!更多关于那个讨厌的输入文件。字段的数量是可变的,并且位置已经改变了几次——我当前的脚本通过分析标题行来确定内容(好吧,fastcsv 和一个狡猾的转换器会这样做)。因此,如果没有多个版本的加载文件,直接上传和后处理 SQL 将无法工作,这太可怕了。它也是一个德语 CSV 文件:分号分隔(没什么大不了的)和用逗号表示的小数点(除非我们加载为 VARCHAR 和之后的文本处理 - 呃,否则更重要的是)。

问题:

以大约 7 秒/秒的速度加载 200 万行将需要超过 24 小时!这可能是日常流程的一个缺点,更不用说用户更希望能够在数据以 CSV 形式出现后大约 5 小时访问数据!

我研究了每次网络旅行应用多个插入:相当笨拙的INSERT ALL... 语法会很好,除了目前我使用序列对每一行应用唯一的 id。事实证明

INSERT ALL
    INTO tablea (id,b,c) VALUES (tablea_seq.nextval,1,2)
    INTO tablea (id,b,c) VALUES (tablea_seq.nextval,3,4)
    INTO tablea (id,b,c) VALUES (tablea_seq.nextval,5,6)
SELECT 1 FROM dual;

(我说这很笨拙吗?)尝试对所有三行使用相同的 id。 Oracle 文档似乎证实了这一点。

最新尝试是在一次执行中发送多个 INSERT,例如:

    INSERT INTO tablea (id,b,c) VALUES (tablea_seq.nextval,1,2);
    INSERT INTO tablea (id,b,c) VALUES (tablea_seq.nextval,3,4);
    INSERT INTO tablea (id,b,c) VALUES (tablea_seq.nextval,5,6);

我还没有找到说服 Oracle 接受的方法。

问题

我是否遗漏了一些明显的东西? (如果结果是这样,我会非常高兴!) 如果我不能发送多个插入,我还能尝试什么?

为什么要接受那个?

无论出于何种原因,我都希望我的代码尽可能不受平台特定结构的影响:出现此问题的一个原因是我正在从 MySQL 迁移到 Oracle;由于地理原因,有一天可能会发生另一次搬迁,我无法确定该平台。因此,让我的数据库库能够使用文本 SQL 命令来实现合理的扩展是很有吸引力的,而 PL/SQL 块实现了这一点。现在,如果确实出现了另一个平台,则更改将仅限于更改代码中的适配器:很可能是单行。

【问题讨论】:

【参考方案1】:

如何将 csv 文件传送到 oracle db 服务器,使用SQLLoader 将 csv 文件加载到临时表中,然后运行存储过程来转换并将其插入到最终表中?

【讨论】:

这可能是最快的方法。您可能也不需要将文件复制到 oracle 服务器;我认为您可以在本地运行 SQLLoader。 是的,您可能会选择在客户端本地运行它。确实非常快。 这是我正在考虑的事情:执行提取和转换阶段以制作 sqlldr 可以消化的内容(因为输入文件格式非常棘手) SQL*Loader 内置了一些非常好的数据操作/转换工具,以及处理可变记录长度和字段数量的能力。 您可以在 SQLLoader 控制文件中指定转换函数。比如“REPLACE(:field1,',','.')”。【参考方案2】:

你可以使用:

insert into tablea (id,b,c)
 ( select tablea_seq.nextval,1,2 from dual union all
   select tablea_seq.nextval,3,4 from dual union all
   select tablea_seq.nextval,3,4 from dual union all
   select tablea_seq.nextval,3,4 from dual union all
   ...
 )

当我没记错时,这一直有效到大约 1024 行。

您也可以将其作为 PL/SQL 批处理指令发送:

BEGIN
 INSERT INTO tablea (id,b,c) VALUES (tablea_seq.nextval,1,2);
 INSERT INTO tablea (id,b,c) VALUES (tablea_seq.nextval,3,4);
 INSERT INTO tablea (id,b,c) VALUES (tablea_seq.nextval,5,6);
 ...
 COMMIT;
END

【讨论】:

我一般使用这里提到的SQL Batch insert,先添加对'ALTER SESSION ENABLE PARALLEL DML'的调用。 第一个,但是第二个......哦,嗬。请稍等,当我去破解我的代码时……那行得通!我想知道它是否可以缩放。请再等一下……嗯。 100 个有效,200 个无效。字符串大小有限制吗? 到目前为止,我还没有达到 PL/SQL 大小的大小限制,但老实说,我认为我的脚本远没有你的脚本那么大。不过,如果您将往返次数减少大约 100 倍(一次插入 100 次而不是 1 次),您应该可以节省大量时间。 有 64000 个“Diana 节点”的限制,这与解析的 Ada 代码有关。我觉得有点像 AST。对于一个小的(5 列)表,我可以进行大约 7500 次插入,或者大约 850KB 的 SQL。 (200 行是我的错误!)【参考方案3】:

我将使用 SQL*Loader 将原始 CSV 文件加载到数据库中的专用表而不对其进行规范化,然后针对表中的数据运行代码以将其规范化为各种所需的表。这将最大限度地减少往返行程,并且在 Oracle 社区中是一种非常传统的方法。

SQL*Loader 在初始学习曲线方面可能有点挑战,但如果您遇到困难,您可以很快再次发布。

http://download.oracle.com/docs/cd/B19306_01/server.102/b14215/part_ldr.htm#i436326

【讨论】:

在没有规范化的情况下工作会很困难:文件具有可变的记录大小、可变的字段数、由列标题确定的位置。它起源于德语,所以小数点是逗号。但是预加载转换绝对是一种选择,尽管它并非没有痛苦。 SQL*Loader 有一些强大的内置数据操作和转换工具。只要您可以在记录中键入一个值,它就可以处理可变的记录大小和列数。 如果您已经编写了解析文件的代码,然后将其吐出为 4 个 csv 文件,那么调用 sql*loader 可能是最有效的方法。【参考方案4】:

我有一个快速的建议。我来自 MySQL 世界,但接受过 Oracle 培训,我认为这会奏效。

在 MySQL 中,您可以使用一条插入语句插入多条记录。它看起来像这样:

INSERT INTO table_name (column_one, column_two, column_three, column_four)
VALUES
    ('a', 'one', 'alpha', 'uno'),        // Row 1
    ('b', 'two', 'beta', 'dos'),         // Row 2
    ('c', 'three', 'gamma', 'tres'),     // etc.
....
    ('z', 'twenty-six', 'omega', 'veintiséis');

现在显然您一次只能插入到一个表中,并且您不想插入 200 万条记录,但您可以轻松地一次插入 10 条、20 条或 100 条(如果允许数据包那么大的话)。您可能必须手动生成此代码,我不知道您正在使用的框架(或任何其他框架)是否支持为您制作此类代码。

在 MySQL 世界中,这极大地加快了插入速度。我假设它会同时进行所有索引更新等,但它也可以防止它必须在每次插入时重新解析 SQL。

如果您将其与准备好的语句(因此 SQL 被缓存并且不必每次都解析出来)和事务(以确保当您必须跨多个插入时事情始终处于正常状态)桌子......我认为你会做得很好。


NunoG 是正确的,您可以将 CSV 直接加载到 Oracle 中。您最好阅读输入文件,生成一组规范化的 CSV 文件(每个表一个),然后一次加载每个文件。

【讨论】:

啊,如果只是!我实际上是从运行良好的 MySQL(长故事)迁移出来的——可惜甲骨文没有想到这一点。不过,PL/SQL 块的东西有点工作。感谢您的意见。【参考方案5】:

您可以将插入写入文本文件,通过网络移动并在本地运行,而不是通过网络执行 SQL。

【讨论】:

不错的横向思维!我也在考虑访问现场的工作站并在那里完成整个工作。【参考方案6】:

SQL*Loader 是 Oracle 提供的一种实用程序,允许您将数据从平面文件加载到一个或多个数据库表中。这将比使用查询的插入快 10-100 倍。

http://www.orafaq.com/wiki/SQL*Loader_FAQ

如果 SQL*Loader 无法解决问题,请尝试使用小型预处理程序将文件格式化为 SQL*Loader 可读格式。

【讨论】:

以上是关于我可以向 Oracle 发送“批量”插入吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何向一个oracle表中快速插入很多条数据

使用 Perl 批量插入 Oracle XMLTYPE

使用 .NET 批量插入到 Oracle

Yii2中的Active Record可以批量插入数据吗

C# 无法向Oracle数据库插入数据

mybatis怎样批量插入数据到oracle,就算id自动增长问题