bad*_*ola 13 sql database oracle oracle-call-interface bulk-load
我正在处理文件加载程序.
此程序的目的是获取输入文件,对其数据进行一些转换,然后将数据上载到Oracle数据库中.
我面临的问题是我需要优化在Oracle上插入非常大的输入数据.
我正在将数据上传到表中,比方说ABC.
我在我的C++程序中使用Oracle提供的OCI库.具体来说,我使用OCI连接池进行多线程并加载到ORACLE.(http://docs.oracle.com/cd/B28359_01/appdev.111/b28395/oci09adv.htm)
以下是用于创建表ABC的DDL语句 -
CREATE TABLE ABC(
seq_no NUMBER NOT NULL,
ssm_id VARCHAR2(9) NOT NULL,
invocation_id VARCHAR2(100) NOT NULL,
analytic_id VARCHAR2(100) NOT NULL,
analytic_value NUMBER NOT NULL,
override VARCHAR2(1) DEFAULT 'N' NOT NULL,
update_source VARCHAR2(255) NOT NULL,
last_chg_user CHAR(10) DEFAULT USER NOT NULL,
last_chg_date TIMESTAMP(3) DEFAULT SYSTIMESTAMP NOT NULL
);
CREATE UNIQUE INDEX ABC_indx ON ABC(seq_no, ssm_id, invocation_id, analytic_id);
/
CREATE SEQUENCE ABC_seq;
/
CREATE OR REPLACE TRIGGER ABC_insert
BEFORE INSERT ON ABC
FOR EACH ROW
BEGIN
SELECT ABC_seq.nextval INTO :new.seq_no FROM DUAL;
END;
Run Code Online (Sandbox Code Playgroud)
我目前正在使用以下查询模式将数据上载到数据库中.我通过各种OCI连接池线程分批发送500个查询数据.
使用SQL插入查询的示例 -
insert into ABC (SSM_ID, invocation_id , calc_id, analytic_id, analytic_value,
override, update_source)
select 'c','b',NULL, 'test', 123 , 'N', 'asdf' from dual
union all select 'a','b',NULL, 'test', 123 , 'N', 'asdf' from dual
union all select 'b','b',NULL, 'test', 123 , 'N', 'asdf' from dual
union all select 'c','g',NULL, 'test', 123 , 'N', 'asdf' from dual
Run Code Online (Sandbox Code Playgroud)
Oracle针对上述查询的执行计划 -
-----------------------------------------------------------------------------
| Id | Operation | Name|Rows| Cost (%CPU) | Time |
-----------------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 4 | 8 (0) | 00:00:01 |
| 1 | LOAD TABLE CONVENTIONAL | ABC | | | |
| 2 | UNION-ALL | | | | |
| 3 | FAST DUAL | | 1 | 2 (0) | 00:00:01 |
| 4 | FAST DUAL | | 1 | 2 (0) | 00:00:01 |
| 5 | FAST DUAL | | 1 | 2 (0) | 00:00:01 |
| 6 | FAST DUAL | | 1 | 2 (0) | 00:00:01 |
Run Code Online (Sandbox Code Playgroud)
该程序的运行时间加载100万行 -
Batch Size = 500
Number of threads - Execution Time -
10 4:19
20 1:58
30 1:17
40 1:34
45 2:06
50 1:21
60 1:24
70 1:41
80 1:43
90 2:17
100 2:06
Average Run Time = 1:57 (Roughly 2 minutes)
Run Code Online (Sandbox Code Playgroud)
我需要进一步优化和减少这个时间.我面临的问题是当我上传1000万行时.
平均运行时间为千万出来是= 21分钟
(我的目标是将这个时间减少到10分钟以下)
所以我也尝试了以下步骤 -
[1]基于seq_no对表ABC进行了分区.使用了30个分区.测试了100万行 - 性能非常差.几乎是未分区表的4倍.
[2]基于last_chg_date对表ABC进行另一次分区.使用了30个分区.
2.a)测试了100万行 - 性能几乎等于未分区的表.差别不大,因此没有考虑到.
2.b)再次测试相同的1000万行.性能几乎等于未分区的表.没有明显的区别.
以下是用于实现分区的DDL命令 -
CREATE TABLESPACE ts1 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts2 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts3 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts4 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts5 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts6 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts7 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts8 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts9 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts10 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts11 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts12 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts13 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts14 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts15 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts16 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts17 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts18 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts19 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts20 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts21 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts22 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts23 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts24 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts25 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts26 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts27 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts28 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts29 DATAFILE AUTOEXTEND ON;
CREATE TABLESPACE ts30 DATAFILE AUTOEXTEND ON;
CREATE TABLE ABC(
seq_no NUMBER NOT NULL,
ssm_id VARCHAR2(9) NOT NULL,
invocation_id VARCHAR2(100) NOT NULL,
calc_id VARCHAR2(100) NULL,
analytic_id VARCHAR2(100) NOT NULL,
ANALYTIC_VALUE NUMBER NOT NULL,
override VARCHAR2(1) DEFAULT 'N' NOT NULL,
update_source VARCHAR2(255) NOT NULL,
last_chg_user CHAR(10) DEFAULT USER NOT NULL,
last_chg_date TIMESTAMP(3) DEFAULT SYSTIMESTAMP NOT NULL
)
PARTITION BY HASH(last_chg_date)
PARTITIONS 30
STORE IN (ts1, ts2, ts3, ts4, ts5, ts6, ts7, ts8, ts9, ts10, ts11, ts12, ts13,
ts14, ts15, ts16, ts17, ts18, ts19, ts20, ts21, ts22, ts23, ts24, ts25, ts26,
ts27, ts28, ts29, ts30);
Run Code Online (Sandbox Code Playgroud)
我在线程函数中使用的代码(用C++编写),使用OCI -
void OracleLoader::bulkInsertThread(std::vector<std::string> const & statements)
{
try
{
INFO("ORACLE_LOADER_THREAD","Entered Thread = %1%", m_env);
string useOraUsr = "some_user";
string useOraPwd = "some_password";
int user_name_len = useOraUsr.length();
int passwd_name_len = useOraPwd.length();
text* username((text*)useOraUsr.c_str());
text* password((text*)useOraPwd.c_str());
if(! m_env)
{
CreateOraEnvAndConnect();
}
OCISvcCtx *m_svc = (OCISvcCtx *) 0;
OCIStmt *m_stm = (OCIStmt *)0;
checkerr(m_err,OCILogon2(m_env,
m_err,
&m_svc,
(CONST OraText *)username,
user_name_len,
(CONST OraText *)password,
passwd_name_len,
(CONST OraText *)poolName,
poolNameLen,
OCI_CPOOL));
OCIHandleAlloc(m_env, (dvoid **)&m_stm, OCI_HTYPE_STMT, (size_t)0, (dvoid **)0);
////////// Execution Queries in the format of - /////////////////
// insert into pm_own.sec_analytics (SSM_ID, invocation_id , calc_id, analytic_id, analytic_value, override, update_source)
// select 'c','b',NULL, 'test', 123 , 'N', 'asdf' from dual
// union all select 'a','b',NULL, 'test', 123 , 'N', 'asdf' from dual
// union all select 'b','b',NULL, 'test', 123 , 'N', 'asdf' from dual
// union all select 'c','g',NULL, 'test', 123 , 'N', 'asdf' from dual
//////////////////////////////////////////////////////////////////
size_t startOffset = 0;
const int batch_size = PCSecAnalyticsContext::instance().getBatchCount();
while (startOffset < statements.size())
{
int remaining = (startOffset + batch_size < statements.size() ) ? batch_size : (statements.size() - startOffset );
// Break the query vector to meet the batch size
std::vector<std::string> items(statements.begin() + startOffset,
statements.begin() + startOffset + remaining);
//! Preparing the Query
std::string insert_query = "insert into ";
insert_query += Context::instance().getUpdateTable();
insert_query += " (SSM_ID, invocation_id , calc_id, analytic_id, analytic_value, override, update_source)\n";
std::vector<std::string>::const_iterator i3 = items.begin();
insert_query += *i3 ;
for( i3 = items.begin() + 1; i3 != items.end(); ++i3)
insert_query += "union " + *i3 ;
// Preparing the Statement and Then Executing it in the next step
text *txtQuery((text *)(insert_query).c_str());
checkerr(m_err, OCIStmtPrepare (m_stm, m_err, txtQuery, strlen((char *)txtQuery), OCI_NTV_SYNTAX, OCI_DEFAULT));
checkerr(m_err, OCIStmtExecute (m_svc, m_stm, m_err, (ub4)1, (ub4)0, (OCISnapshot *)0, (OCISnapshot *)0, OCI_DEFAULT ));
startOffset += batch_size;
}
// Here is the commit statement. I am committing at the end of each thread.
checkerr(m_err, OCITransCommit(m_svc,m_err,(ub4)0));
checkerr(m_err, OCIHandleFree((dvoid *) m_stm, OCI_HTYPE_STMT));
checkerr(m_err, OCILogoff(m_svc, m_err));
INFO("ORACLE_LOADER_THREAD","Thread Complete. Leaving Thread.");
}
catch(AnException &ex)
{
ERROR("ORACLE_LOADER_THREAD", "Oracle query failed with : %1%", std::string(ex.what()));
throw AnException(string("Oracle query failed with : ") + ex.what());
}
}
Run Code Online (Sandbox Code Playgroud)
在回复帖子时,我提出了几种方法来优化我的INSERT QUERY.我在我的程序中选择并使用了QUERY I,原因如下我在测试各种INSERT查询时发现的原因.在运行向我建议的SQL查询 - QUERY I -
insert into ABC (SSM_ID, invocation_id , calc_id, analytic_id, analytic_value,
override, update_source)
select 'c','b',NULL, 'test', 123 , 'N', 'asdf' from dual
union all select 'a','b',NULL, 'test', 123 , 'N', 'asdf' from dual
union all select 'b','b',NULL, 'test', 123 , 'N', 'asdf' from dual
union all select 'c','g',NULL, 'test', 123 , 'N', 'asdf' from dual
Run Code Online (Sandbox Code Playgroud)
Oracle针对查询I的执行计划 -
--------------------------------------------------------------------------
| Id | Operation | Name| Rows | Cost (%CPU) | Time |
--------------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 4 | 8 (0) | 00:00:01 |
| 1 | LOAD TABLE CONVENTIONAL | ABC | | | |
| 2 | UNION-ALL | | | | |
| 3 | FAST DUAL | | 1 | 2 (0) | 00:00:01 |
| 4 | FAST DUAL | | 1 | 2 (0) | 00:00:01 |
| 5 | FAST DUAL | | 1 | 2 (0) | 00:00:01 |
| 6 | FAST DUAL | | 1 | 2 (0) | 00:00:01 |
Run Code Online (Sandbox Code Playgroud)
QUERY II -
insert all
into ABC (SSM_ID, invocation_id , calc_id, analytic_id, analytic_value,
override, update_source) values ('c','b',NULL, 'test', 123 , 'N', 'asdf')
into ABC (SSM_ID, invocation_id , calc_id, analytic_id, analytic_value,
override, update_source) values ('c','e',NULL, 'test', 123 , 'N', 'asdf')
into ABC (SSM_ID, invocation_id , calc_id, analytic_id, analytic_value,
override, update_source) values ('c','r',NULL, 'test', 123 , 'N', 'asdf')
into ABC (SSM_ID, invocation_id , calc_id, analytic_id, analytic_value,
override, update_source) values ('c','t',NULL, 'test', 123 , 'N', 'asdf')
select 1 from dual
Run Code Online (Sandbox Code Playgroud)
Oracle针对查询II的执行计划 -
-----------------------------------------------------------------------------
| Id | Operation | Name| Rows | Cost (%CPU) | Time |
-----------------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 1 | 2 (0) | 00:00:01 |
| 1 | MULTI-TABLE INSERT | | | | |
| 2 | FAST DUAL | | 1 | 2 (0) | 00:00:01 |
| 3 | INTO | ABC | | | |
| 4 | INTO | ABC | | | |
| 5 | INTO | ABC | | | |
| 6 | INTO | ABC | | | |
Run Code Online (Sandbox Code Playgroud)
根据实验,查询I更快.
在这里,我在Oracle SQL Developer上进行了测试,并且我也通过我的C++程序(FILELOADER)发送了插入查询.
在进一步阅读它时,我发现执行计划显示的成本是查询用于处理自身的CPU数量. 这告诉Oracle将使用更多的CPU来处理第一个查询,这就是为什么它的成本为= 8.
即使通过我的应用程序使用相同的插入模式,我发现它的性能几乎要好1.5倍.
我需要了解如何进一步提高性能..?我尝试过的所有事情,都是在我的问题中总结出来的.如果我发现或发现任何相关内容,我将添加此问题.
我的目标是在10分钟内上传1000万次查询.
我知道其他人已经提到了这一点,你不想听,但使用SQL*Loader或外部表.对于超过10米的行,我的平均加载时间大约相同,宽度为12.57 秒.这些实用程序已明确设计为快速将数据加载到数据库中并且非常擅长.根据输入文件的格式,这可能会产生一些额外的时间处罚,但是有很多选项,我很少在加载之前更改文件.
如果您不愿意这样做,那么您不必升级您的硬件; 你需要消除所有可能阻碍快速加载的障碍.要枚举它们,请删除:
有了所有这些,你就不得不让数据库执行更多工作,而且因为你在事务上这样做,所以你没有充分发挥数据库的潜力.
比如说,将数据加载到一个单独的表中ABC_LOAD.数据完全加载后,在ABC中执行单个 INSERT语句.
insert into abc
select abc_seq.nextval, a.*
from abc_load a
Run Code Online (Sandbox Code Playgroud)
执行此操作时(即使您不这样做)确保序列缓存大小正确; 引用:
当应用程序访问序列缓存中的序列时,会快速读取序列号.但是,如果应用程序访问不在高速缓存中的序列,则必须在使用序列号之前将序列从磁盘读取到高速缓存.
如果您的应用程序同时使用多个序列,那么序列缓存可能不足以容纳所有序列.在这种情况下,访问序列号可能通常需要磁盘读取.要快速访问所有序列,请确保您的缓存有足够的条目来保存应用程序同时使用的所有序列.
这意味着如果您有10个线程同时使用此序列写入500条记录,那么您需要一个5,000的缓存大小.在ALTER SEQUENCE文件指出如何改变这样的:
alter sequence abc_seq cache 5000
Run Code Online (Sandbox Code Playgroud)
如果你按照我的建议我将缓存大小提升到大约10.5米.
查看使用APPEND提示 (另请参阅Oracle Base) ; 这指示Oracle使用直接路径插入,它直接将数据附加到表的末尾,而不是寻找空间来放置它.如果你的表有索引但你可以使用它,你将无法使用它ABC_LOAD
insert /*+ append */ into ABC (SSM_ID, invocation_id , calc_id, ... )
select 'c','b',NULL, 'test', 123 , 'N', 'asdf' from dual
union all select 'a','b',NULL, 'test', 123 , 'N', 'asdf' from dual
union all select 'b','b',NULL, 'test', 123 , 'N', 'asdf' from dual
union all select 'c','g',NULL, 'test', 123 , 'N', 'asdf' from dual
Run Code Online (Sandbox Code Playgroud)
如果您使用APPEND提示; 在插入之后我会添加TRUNCATE ABC_LOAD,ABC否则这个表会无限增长.这应该是安全的,因为到那时你将完成使用该表.
您没有提到您正在使用的版本或版本或Oracle.你可以使用一些额外的小技巧:
Oracle 12c
此版本支持标识列 ; 你可以完全摆脱序列.
CREATE TABLE ABC(
seq_no NUMBER GENERATED AS IDENTITY (increment by 5000)
Run Code Online (Sandbox Code Playgroud)Oracle 11g r2
如果你保持扳机; 您可以直接分配序列值.
:new.seq_no := ABC_seq.nextval;
Run Code Online (Sandbox Code Playgroud)Oracle企业版
如果您正在使用Oracle Enterprise,则可以ABC_LOAD使用PARALLEL提示来加速INSERT :
insert /*+ parallel */ into abc
select abc_seq.nextval, a.*
from abc_load a
Run Code Online (Sandbox Code Playgroud)
这可能导致它自己的问题(太多的并行进程等),所以测试.它可能有助于较小的批量插入,但它不太可能,因为你将浪费时间计算什么线程应该处理什么.
使用数据库附带的实用程序.
如果你不能使用它们,那么就可以摆脱所有可能减慢插入速度并放大批量生成的东西,因为这是数据库擅长的.
| 归档时间: |
|
| 查看次数: |
13583 次 |
| 最近记录: |