perl DBI 를 사용하여 multi row Insert 하는 부분 질문입니다.
글쓴이: jip102 / 작성시간: 화, 2013/11/12 - 5:29오후
안녕하세요. 여러분
Perl DBI를 사용하여 Mulirow를 Insert 를 하려 합니다.
Insert 하는 방식은 데이터 파일(test.csv)을 읽어서 file Open 후 테이블에 insert 를 하려는데 잘 안되는군요
데이터 파일(test.csv)의 내용은 아래와 같습니다.(약 70만건 됩니다.)
21,spderp01,CPU Usage,2013-10-01 10:09:03,8.48
22,spderp02,CPU Usage,2013-10-01 10:09:05,.48
.
.
22,spderp02,CPU Usage,2013-10-01 10:09:05,.48
위의 데이터 파일을 읽어서 테이블에 insert를 하는데 bind 관련 에러가 발생해서, Insert 의 Multi Row 도 select 처럼
while 로 fetch를 해서 실행해야 하나요? 제가 테스트 해본 소스는 아래와 같은데 쉽제 않군요... 좀 도와 주세요
my $textfile = "test.csv";
open (TEXT, "$textfile") or die "Couldn't open textfile: $!";
{
local $/ = undef;
$dbh->do("INSERT INTO SUM_CPU values (?,?,?,?,?)", undef, );
}
Forums:
아 죄송.. 구문이 하나 빠졌네여(undef, <TEXT>);
소스코드를 다시 올립니다.
undef 다음에
,); 빠졌네여..
질문에 올리신 코드를 보면 아래와 같이 DB에
질문에 올리신 코드를 보면 아래와 같이 DB에 insert문의 문자열을 바로 던지는 식인데요. DBI 사용법에 맞는지 아닌지를 떠나서, 이런 형태로 작성하시면 DB에서 매번 insert문을 분석하는 과정을 거치게 되므로 다수의 row를 입력하는데 매우 오랜 시간이 걸리게 됩니다.
[1] 아래와 같이 우선 insert statement를 prepare해두고 이후엔 이것을 재활용하여 입력데이터만 바꿔가면서 execute()를 반복하는 것이 빠릅니다.
[2] 그리고, 매 insert 문마다 autocommit이 일어나게 방치하지 말고, 아래 코드와 같이 일정횟수마다 한 번씩 몰아서 transaction을 commit해주면 훨씬 더 (수십배쯤?) 빨라집니다. 저는 5000건씩 묶어서 commit했습니다 (1000건씩 commit하게 하면 시간이 약간 더 걸리더군요. 적절한 갯수는 자신의 환경에서 테스트로 정하면 되겠죠).
참고로, $dbh->begin_work; 문이 등장하면 autocommit이 일시적으로 꺼지고, $dbh->commit; 이나 $dbh->roolback; 이 등장하면 autocommit이 다시 켜지게 됩니다. DB connect할때나 그 직후에 autocommit을 꺼둘수도 있지만 굳이 그렇게 하지 않아도 transaction 관리가 필요할 때 위의 명령을 적당히 끼워넣어 일시적으로 통제하는 것만으로도 성능개선에는 충분한 것 같습니다.
8만건짜리 가짜데이터를 만들어 SQLite에서 실험해보니 [1]은 2분 좀 넘게, [2]는 1.3초 걸렸습니다.
80만건으로 늘려보니 [2]가 12.5초 걸리네요. [1]은 안해봤습니다. 아마 20분쯤 걸리겠죠.
PostgreSQL-8.4에서 해보니 8만개 insert에 [2]방식이 6.7초 정도였습니다. 기본설치상태에서 별 노력없이 돌려본 것이니까 최적화같은 것을 하면 달라질 수 있는 부분도 있을 것입니다. 어떤 DB를 쓰시는지 모르겠지만 이런 두 방식간의 성능차는 그 정도는 달라질 수 있어도 어느 DB에든 적용될 것입니다.
참고자료 : How do I improve the performance of SQLite?
http://stackoverflow.com/questions/1711631/how-do-i-improve-the-performance-of-sqlite
C로 작성된 샘플과 함께 성능개선에 대해 설명하고 있는 자료인데 perl은 아니지만 기초가 되는 내용이므로 유용할 것입니다.
TODO: DB에 따라서는 bulk insert같은 것을 지원하는 경우도 있습니다. 한 예로, oracle이 있는데요. 이 경우 DB를 오가는 횟수가 획기적으로 줄어들게 됩니다. [2]의 예에서 DB를 5000번 오갈때마다 transaction을 마무리하는 식으로도 큰 성능개선이 이뤄졌는데요, bulk insert를 쓰게 되면 5000개의 데이터를 DB에 한 번에 넘겨주고 transaction을 마무리하는 셈이 될 것입니다. 이렇게 해서 추가로 얻어지는 개선폭이 어느정도인지는 모르겠습니다만 데이터가 극히 많은 상황에서는 이런 고려가 필요하기도 할 겁니다. 단점은, DB마다 나름대로 SQL문을 확장하여 제공하는 방식이므로 DB별로 문장형태가 조금씩 다를 것이라는 점입니다.
아래와 같이 입력방식을 달리했을때,[1]
아래와 같이 입력방식을 달리했을때,
[1] autocommit : commit per 1 insert
[2] 트랜잭션관리 : commit per 5000 inserts
[3] bulk insert : 5000 rows per insert (execute_array() 이용)
Oracle 10gR2-XE 에서 80만건 insert 실험해보니 아래와 같이 나옵니다.
(DB는 같은PC에 있었지만 접속은 Listener를 통해서 했음)
[1] 4분30초
[2] 1분50초
[3] 17초
PostgreSQL-8.4는,
[1] 4분50초
[2] 1분12초
[3] [2]와 비슷
SQLite-3.3.6-7은,
[1] 21분17초
[2] 12.5초
[3] 18초 (?)
실험은 많이 반복한 것은 아니고 그냥 몇 번 정도씩만 해본 것입니다.
Oracle에서 꽤 효과를 보인 bulk insert는 말은 그렇게 붙였지만 차이는 그냥 execute_array()를 실행한 것 뿐입니다. 각 DBD별로 하부가 얼마나 잘 구현되어 있는가에 따라 execute_array()가 상당한 개선효과를 보이기도 하고 오히려 역효과를 보이기도 하는 것이 아닐까 추측합니다.
파일 정리하다가 위의 [3]방식인
파일 정리하다가 위의 [3]방식인 insert_array()를 써서 작성해둔 코드가 눈에 띄어 올려둡니다.
오라클에서는 꽤 큰 성능향상이 가능했으니, 필요하신 분 참고하시기 바랍니다.
우선 실험용 데이터 생성하는 코드입니다.
실행하면 80만줄을 표준출력으로 뿜어내므로 아래와 같이 쓰면 됩니다.
다음으로, 오라클 DB에 insert_array()를 써서 데이터를 insert하는 코드입니다.
DB에 SUM_CPU 테이블이 있는지 확인해보고,
없으면 생성하고 multiline.txt 의 데이터를 읽어 모두 insert 한 다음 테이블 내용물을 select 합니다.
테이블이 이미 있으면 insert 생략하고 그냥 select가 실행됩니다.
http://gentooboy.tistory.com/136 에 있던 python sqlite 샘플을 가져다 perl oracle 로 고쳐쓴 것입니다.
자세한 답변 감사합니다.
정말 자세한 답변 감사합니다.
이런 내공은 언제쯤 가지게 될까여...
잘 참조하여 쓰도록 하겠습니다.
감사합니다.
근데 파일의 1건은 처리하는데...
1건은 읽어서 insert 처리를 하는데
1건이상의 데이터를 읽어서 처리를 하니
oracle 에러가 나네여(ORA-01722 : invalid number)
insert 되어지는 컬럼은 number 형식으로 맞게 되어 있는데 파일을 읽으면서
형변환이 일어나는 듯 합니다.
해결책이 없을까요?
오류번호 하나만으로는 제3자가 문제를 추적하기
오류번호 하나만으로는 제3자가 문제를 추적하기 어렵습니다. 오류 재현에 필요한 정보를 올려주시면 테스트해보겠습니다. 아래 내용 정도가 되겠죠. 노출이 곤란한 민감한 정보는 적당히 지우시고요.
- 테이블 만드는 create table 문
- 오류재현이 가능한 perl 샘플 코드
- 오류재현에 필요한 text 데이터 몇 줄
네 답변 감사합니다. 관련사항에 대해 파일첨부해서 올려 봅니다.
안녕하세요. 우선 상세한 답변 너무 감사드립니다.
이소스의 목적은 월별로 호스트 name별 CPU 사용량(%)이 파일로 생성되면
생성된 파일을 읽어서 테이블에 insert하는 작업입니다.Insert 전 데이터를 Truncate 합니다.
관련된 정보를 파일로 첨부하여 올려 봅니다.
- 테이블 만드는 create table 문
--> crt_table.sql
- 오류재현이 가능한 perl 샘플 코드
--> data_insert.pl
- 오류재현에 필요한 text 데이터 몇 줄
--> cpu_usage_201310.csv
아래와 같이 고쳐보니 잘 되는군요. my
아래와 같이 고쳐보니 잘 되는군요.
문제되는 부분이 두 군데였습니다. date 타입의 컬럼과 NUMBER 타입의 컬럼인데, date 타입은 prepare()할때 TO_DATE 함수를 끼워서 해결했고, NUMBER 컬럼은 execute()할때 $arr[4] 문자열을 슬쩍 실수 타입으로 변환해서 넘기는 식으로 해결했습니다.
NUMBER 타입의 문제만 말씀하신 것을 보니 DATE 타입의 문제는 겪지 않으신 것 같은데요. jip102님은 DBD-ODBC를 쓰셨고, 저는 DBD-Oracle 드라이버를 썼는데 그 때문에 생긴 차이인지도 모르겠습니다.
맞아요. odbc 타입의 DBD는 DATE 타입에 대해선 형변환이 자동으로 같아요
우선 답변 감사드립니다.
아. 저런식으로 배열에 실수 타입을 cast시킬수 있군요.
저도 비슷한 방법으로 배열에 형변환 되는 함수를 씌우면 되는줄 줄 알았는데 찾아보니 없더라구요
예를 들면 뭐 이런식으로 INT($arr[0])
여하튼 한수 배웠습니다. 다시한번 감사드립니다.
Perl 에서 Oracle Hint 가능할까요?
chanik 님 Oracle 의 Hint를 써볼려고 하는데
$sth = $dbh->prepare("INSERT INTO \/\*\+ PARALLEL(SUM_CPU 4) \*\/ INTO SUM_CPU VALUES(?, ?, ?, ?, ?)")
혹은
$sth = $dbh->prepare( q{INSERT INTO /*+ PARALLEL(SUM_CPU 4) */ INTO SUM_CPU VALUES(?, ?, ?, ?, ?)})
해보았는데
ORA-01861: literal does not match format string 에러가 나는군요
Perl 에서도 Oracle Hint가 가능하나요?
아래와 같이 INTO 키워드가 중복되어 있던 것을
아래와 같이 INTO 키워드가 중복되어 있던 것을 제거하니 잘 되네요. 오류를 내지는 않았지만 SUM_CPU와 4 사이에 ','도 추가했습니다. 백슬래시를 이용한 이스케이핑은 하든 안하든 오류는 안 나네요.
저는 DBD-ODBC가 아닌 DBD-Oracle 드라이버를 썼습니다. 만약 위의 prepare문이 실패한다면, 혹시 ODBC 계층에서 명령 문자열을 변조하는 것은 아닌지 의심됩니다. http://support.microsoft.com/kb/274551 을 보니 ODBC를 거쳐가는 SQL문을 추적하는 기능이 ODBC 관리자에 있는것 같으니 참고하시고요..
아 죄송.. 문법 에러였네여
문법 에러이네여. ㅋㅋ 죄송~
테스트 해보니까 어차피 While Loop로 뺑뺑이 돌면서 하는거라 Hint 자체 의미가 없군요..
ㅎㅎ
Oracle에서 데이터 입력 시간을 줄이려면,
Oracle에서 데이터 입력 시간을 줄이려면, execute() 대신 execute_array()를 쓰십시오.
http://kldp.org/node/140697#comment-600165 에 좀 엉성하지만 동작시간을 올려뒀습니다.
답변 감사합니다.
정말 감사합니다.
chanik님 질문 하나만 더 ㅎㅎ
예전에 올렸던 질문에서 답변을 받았던 내용이긴 합니다만, cpu_usage.txt 라는 파일에 아래와 같은 내용이 있습니다.
select * from HISTORY1 a,host b where a.param_name='CPU Usage' union all
select * from HISTORY2 a,host b where a.param_name='CPU Usage' union all
select * from HISTORY3 a,host b where a.param_name='CPU Usage' union all
select * from HISTORY4 a,host b where a.param_name='CPU Usage' union all
select * from HISTORY5 a,host b where a.param_name='CPU Usage' union all
그때 질문 드렸던건 마지막 라인의 union all 만 ';'으로 바꾸는 방법이었는데 위의 파일을 cat으로 읽어서 다시 가공하는 방법이었습니다.
cat cpu_usage.txt | perl -ne print $last; $last = $_; END { $last =~ s/\s*union all/;/i; print $last}'
위의 부분을 File Open 문을 이용하여 할 수는 없을까요?
제가 테스트를 해본결과 if문을 써서 union all 매칭되는 키워드를 ';' 로 바꾸기는 하는데 전부 바껴 버려서..(아직 내공이 부족하군요)
Open 문을 사용하여 마지막 라인만 ';' 바꾸는게 가능할까요?
http://kldp.org/node/140639#c
http://kldp.org/node/140639#comment-600196 에 답글 달았습니다.
주제와 직접 관련되지 않은 질문을 글타래에 섞으면 다른 분들이 참고하기가 어려워집니다.
이 질문에 대한 내용은 원래의 질문글에 모으는 것이 나을 것 같아 그 글에 달아뒀습니다.
넵 알겠습니다.~
네 생각이 좀 짧았습니다.
감사합니다.
댓글 달기