c - 성능 - sqlite 란




SQLite의 INSERT/초 성능 향상? (6)

벌크 인서트

이 게시물과 스택 오버플로 질문에 영감을 받아 여기서 나를 이끌어 냈습니다. - SQLite 데이터베이스에서 한 번에 여러 행을 삽입 할 수 있습니까? - 내 첫 번째 Git 저장소를 게시했습니다.

https://github.com/rdpoor/CreateOrUpdate

ActiveRecord 배열을 MySQL , SQLite 또는 PostgreSQL 데이터베이스에 대량로드합니다. 기존 레코드를 무시하거나 덮어 쓰거나 오류를 발생시키는 옵션이 포함되어 있습니다. 제 기본 벤치마킹은 순차 쓰기 인 YMMV에 비해 속도가 10 배 향상되었습니다.

대형 데이터 세트를 자주 가져와야하는 프로덕션 코드에서이 코드를 사용하고 있습니다.

SQLite 최적화는 까다 롭습니다. C 응용 프로그램의 대량 삽입 성능은 초당 85 인서트에서 초당 96,000 인서트까지 다양합니다.

배경 : 데스크톱 응용 프로그램의 일부로 SQLite를 사용하고 있습니다. 우리는 응용 프로그램이 초기화 될 때 추가 처리를 위해 SQLite 데이터베이스에 구문 분석되고로드되는 많은 양의 구성 데이터를 XML 파일에 저장합니다. SQLite는 빠르고, 특수한 구성이 필요하지 않으며, 데이터베이스가 단일 파일로 디스크에 저장되기 때문에 이러한 상황에 이상적입니다.

이론적 설명 : 처음에는 내가 본 공연에 실망했습니다. 데이터베이스 구성 방법과 API 사용 방법에 따라 SQLite의 성능이 크게 달라질 수 있습니다 (대량 삽입 및 선택 모두). 모든 옵션과 기술이 무엇인지 알아내는 것은 사소한 일이 아니므로 다른 사람들에게 같은 조사의 문제를 저장하기 위해이 커뮤니티 위키 항목을 작성하여 스택 오버플로 독자와 결과를 공유하는 것이 현명하다고 생각했습니다.

실험 : 단순히 일반적인 의미의 성능 팁 (예 : "트랜잭션 사용" )에 대해 이야기하기보다는 C 코드를 작성하고 다양한 옵션의 영향을 실제로 측정 하는 것이 가장 좋습니다. 간단한 데이터로 시작하겠습니다.

  • 토론토시 전체 전송 일정 중 28MB TAB로 구분 된 텍스트 파일 (약 865,000 레코드)
  • 내 테스트 컴퓨터는 Windows XP를 실행하는 3.60GHz P4입니다.
  • 이 코드는 Visual C ++ 2005로 "전체 최적화"(/ Ox) 및 호의 고속 코드 (/ Ot)를 사용하여 "릴리스"로 컴파일됩니다.
  • SQLite "Amalgamation"을 사용하고 있습니다. 직접 테스트 응용 프로그램으로 컴파일됩니다. 내가 가진 SQLite 버전은 약간 오래된 버전이지만 (3.6.7),이 결과가 최신 릴리스와 비교 될 것으로 생각됩니다. (다르게 생각한다면 의견을 남겨주세요).

코드를 작성해 봅시다!

The Code : 텍스트 파일을 한 줄씩 읽고 문자열을 값으로 나눈 다음 SQLite 데이터베이스에 데이터를 삽입하는 간단한 C 프로그램입니다. 이 "기준선"버전의 코드에서는 데이터베이스가 생성되지만 실제로 데이터를 삽입하지는 않습니다.

/*************************************************************
    Baseline code to experiment with SQLite performance.

    Input data is a 28 MB TAB-delimited text file of the
    complete Toronto Transit System schedule/route info
    from http://www.toronto.ca/open/datasets/ttc-routes/

**************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "sqlite3.h"

#define INPUTDATA "C:\\TTC_schedule_scheduleitem_10-27-2009.txt"
#define DATABASE "c:\\TTC_schedule_scheduleitem_10-27-2009.sqlite"
#define TABLE "CREATE TABLE IF NOT EXISTS TTC (id INTEGER PRIMARY KEY, Route_ID TEXT, Branch_Code TEXT, Version INTEGER, Stop INTEGER, Vehicle_Index INTEGER, Day Integer, Time TEXT)"
#define BUFFER_SIZE 256

int main(int argc, char **argv) {

    sqlite3 * db;
    sqlite3_stmt * stmt;
    char * sErrMsg = 0;
    char * tail = 0;
    int nRetCode;
    int n = 0;

    clock_t cStartClock;

    FILE * pFile;
    char sInputBuf [BUFFER_SIZE] = "\0";

    char * sRT = 0;  /* Route */
    char * sBR = 0;  /* Branch */
    char * sVR = 0;  /* Version */
    char * sST = 0;  /* Stop Number */
    char * sVI = 0;  /* Vehicle */
    char * sDT = 0;  /* Date */
    char * sTM = 0;  /* Time */

    char sSQL [BUFFER_SIZE] = "\0";

    /*********************************************/
    /* Open the Database and create the Schema */
    sqlite3_open(DATABASE, &db);
    sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);

    /*********************************************/
    /* Open input file and import into Database*/
    cStartClock = clock();

    pFile = fopen (INPUTDATA,"r");
    while (!feof(pFile)) {

        fgets (sInputBuf, BUFFER_SIZE, pFile);

        sRT = strtok (sInputBuf, "\t");     /* Get Route */
        sBR = strtok (NULL, "\t");            /* Get Branch */
        sVR = strtok (NULL, "\t");            /* Get Version */
        sST = strtok (NULL, "\t");            /* Get Stop Number */
        sVI = strtok (NULL, "\t");            /* Get Vehicle */
        sDT = strtok (NULL, "\t");            /* Get Date */
        sTM = strtok (NULL, "\t");            /* Get Time */

        /* ACTUAL INSERT WILL GO HERE */

        n++;
    }
    fclose (pFile);

    printf("Imported %d records in %4.2f seconds\n", n, (clock() - cStartClock) / (double)CLOCKS_PER_SEC);

    sqlite3_close(db);
    return 0;
}

제어"

있는 그대로 코드를 실행해도 실제로 데이터베이스 작업은 수행되지 않지만 원시 C 파일 I / O 및 문자열 처리 작업이 얼마나 빠르는지 알 수 있습니다.

0.94 초에 864913 개의 레코드 가져 오기

큰! 우리는 실제로 삽입을하지 않으면 초당 920,000의 삽입을 할 수 있습니다 :-)

"최악의 시나리오"

우리는 파일에서 읽은 값을 사용하여 SQL 문자열을 생성하고 sqlite3_exec를 사용하여 SQL 작업을 호출합니다.

sprintf(sSQL, "INSERT INTO TTC VALUES (NULL, '%s', '%s', '%s', '%s', '%s', '%s', '%s')", sRT, sBR, sVR, sST, sVI, sDT, sTM);
sqlite3_exec(db, sSQL, NULL, NULL, &sErrMsg);

이것은 모든 삽입에 대해 SQL이 VDBE 코드로 컴파일되고 모든 삽입이 자체 트랜잭션에서 발생하기 때문에 느려질 것입니다. 얼마나 천천히?

9933.61 초에 864913 개의 레코드를 가져 왔습니다.

Yikes! 2 시간 45 분! 초당 85 개의 인서트있습니다.

트랜잭션 사용

기본적으로 SQLite는 고유 한 트랜잭션 내에서 모든 INSERT / UPDATE 문을 평가합니다. 많은 수의 삽입 작업을 수행하는 경우 작업을 트랜잭션으로 래핑하는 것이 좋습니다.

sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);

pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {

    ...

}
fclose (pFile);

sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);

38.03 초 만에 864913 개의 레코드 가져 오기

그게 낫다. 단일 트랜잭션에서 모든 인서트를 간단하게 배치하면 성능이 초 당 23,000 인서트로 향상되었습니다 .

Prepared Statement 사용하기

트랜잭션을 사용하는 것은 엄청난 개선 이었지만 동일한 SQL을 반복해서 사용하는 경우 모든 삽입에 대해 SQL 문을 다시 컴파일하는 것은 의미가 없습니다. sqlite3_prepare_v2 를 사용하여 SQL 문을 한 번 컴파일 한 다음 sqlite3_bind_text 사용하여 매개 변수를 해당 문에 바인딩합니다.

/* Open input file and import into the database */
cStartClock = clock();

sprintf(sSQL, "INSERT INTO TTC VALUES (NULL, @RT, @BR, @VR, @ST, @VI, @DT, @TM)");
sqlite3_prepare_v2(db,  sSQL, BUFFER_SIZE, &stmt, &tail);

sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);

pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {

    fgets (sInputBuf, BUFFER_SIZE, pFile);

    sRT = strtok (sInputBuf, "\t");   /* Get Route */
    sBR = strtok (NULL, "\t");        /* Get Branch */
    sVR = strtok (NULL, "\t");        /* Get Version */
    sST = strtok (NULL, "\t");        /* Get Stop Number */
    sVI = strtok (NULL, "\t");        /* Get Vehicle */
    sDT = strtok (NULL, "\t");        /* Get Date */
    sTM = strtok (NULL, "\t");        /* Get Time */

    sqlite3_bind_text(stmt, 1, sRT, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 2, sBR, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 3, sVR, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 4, sST, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 5, sVI, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 6, sDT, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 7, sTM, -1, SQLITE_TRANSIENT);

    sqlite3_step(stmt);

    sqlite3_clear_bindings(stmt);
    sqlite3_reset(stmt);

    n++;
}
fclose (pFile);

sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);

printf("Imported %d records in %4.2f seconds\n", n, (clock() - cStartClock) / (double)CLOCKS_PER_SEC);

sqlite3_finalize(stmt);
sqlite3_close(db);

return 0;

16.27 초 동안 864913 개의 레코드를 가져 왔습니다.

좋은! 조금 더 많은 코드가 있습니다 ( sqlite3_clear_bindingssqlite3_reset 을 호출하는 것을 잊지 마십시오). 그러나 우리는 초당 53,000 인서트로 성능을 두 배 이상 향상 시켰습니다 .

PRAGMA 동기 = 꺼짐

기본적으로 SQLite는 OS 수준의 쓰기 명령을 실행 한 후 일시 중지됩니다. 이렇게하면 데이터가 디스크에 기록됩니다. synchronous = OFF 로 설정함으로써, 우리는 SQLite에게 쓰기를 위해 데이터를 간단히 핸드 오프시킨 다음 계속하도록 지시합니다. 데이터가 플래터에 기록되기 전에 컴퓨터가 치명적인 충돌 (또는 정전)을 겪을 경우 데이터베이스 파일이 손상 될 수 있습니다.

/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA synchronous = OFF", NULL, NULL, &sErrMsg);

가져온 864913 레코드 12.41 초

개선 된 부분은 작지만 초당 최대 69,600 개의 인서트가 있습니다.

PRAGMA journal_mode = MEMORY

PRAGMA journal_mode = MEMORY 평가하여 롤백 저널을 메모리에 저장하는 것을 고려하십시오. 거래가 더 빠르지 만, 거래 중에 권력을 잃거나 프로그램이 중단되면 부분적으로 완료된 거래로 데이터베이스가 손상된 상태로 남을 수 있습니다.

/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA journal_mode = MEMORY", NULL, NULL, &sErrMsg);

13.50 초에 864913 개의 레코드 가져 오기

초당 64,000 인서트 의 이전 최적화보다 약간 느립니다 .

PRAGMA 동기식 = OFF PRAGMA journal_mode = MEMORY

이전 두 최적화를 결합합시다. 충돌이 발생한 경우 조금 더 위험하지만 데이터를 가져 오는 것입니다 (은행을 운영하지 않음).

/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA synchronous = OFF", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA journal_mode = MEMORY", NULL, NULL, &sErrMsg);

12.00 초에 864913 개의 레코드 가져 오기

환상적! 초당 72,000 개의 인서트 를 처리 할 수 있습니다.

메모리 내 데이터베이스 사용

단지 킥을 위해서 이전의 모든 최적화를 토대로 데이터베이스 파일 이름을 다시 정의하여 완전히 RAM에서 작업하도록합시다.

#define DATABASE ":memory:"

10.94 초에 864913 개의 레코드 가져 오기

RAM에 데이터베이스를 저장하는 것은 실용적이지는 않지만 초당 79,000 개의 인서트를 수행 할 수 있다는 점은 인상적입니다 .

리팩토링 C 코드

특별히 SQLite 개선은 아니지만 while 루프에서 여분의 char* 할당 연산을 좋아하지 않습니다. strtok() 의 결과를 sqlite3_bind_text() 직접 전달하고 컴파일러에서 우리를 위해 속도를 높이려는 코드를 리팩터링 해 봅시다.

pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {

    fgets (sInputBuf, BUFFER_SIZE, pFile);

    sqlite3_bind_text(stmt, 1, strtok (sInputBuf, "\t"), -1, SQLITE_TRANSIENT); /* Get Route */
    sqlite3_bind_text(stmt, 2, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Branch */
    sqlite3_bind_text(stmt, 3, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Version */
    sqlite3_bind_text(stmt, 4, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Stop Number */
    sqlite3_bind_text(stmt, 5, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Vehicle */
    sqlite3_bind_text(stmt, 6, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Date */
    sqlite3_bind_text(stmt, 7, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Time */

    sqlite3_step(stmt);        /* Execute the SQL Statement */
    sqlite3_clear_bindings(stmt);    /* Clear bindings */
    sqlite3_reset(stmt);        /* Reset VDBE */

    n++;
}
fclose (pFile);

참고 : 실제 데이터베이스 파일을 사용하는 것으로 돌아갑니다. 인 메모리 데이터베이스는 빠르지 만 반드시 실용적인 것은 아닙니다.

8.94 초에 864913 개의 레코드 가져 오기

매개 변수 바인딩에 사용 된 문자열 처리 코드에 대한 약간의 리팩토링으로 초 당 96,700 개의 삽입 을 수행 할 수있었습니다 . 이것이 충분히 빠르다고 말하는 것이 안전하다고 생각합니다. 다른 변수 (예 : 페이지 크기, 색인 작성 등)를 조정하기 시작하면 이것이 우리의 벤치 마크가됩니다.

요약 (지금까지)

나 아직도 나와 함께 있기를 바란다. 우리가이 길을 시작한 이유는 대량 삽입 성능이 SQLite와 크게 다르다는 점과, 작업 속도를 높이기 위해 변경해야 할 사항이 언제나 명백한 것은 아니기 때문입니다. 동일한 컴파일러 (및 컴파일러 옵션)를 사용하여 SQLite의 동일한 버전과 동일한 데이터를 사용하여 코드 및 SQLite 사용을 최적화 하여 최악의 시나리오에서 초 당 85 개의 인서트를 초당 96,000 개의 인서트로 전환합니다!

CREATE INDEX를 누른 다음 INSERT를 누른 다음 INSERT를 누른 다음 INDEX를 작성하십시오.

SELECT 성능을 측정하기 전에 인덱스를 작성한다는 것을 알고 있습니다. 아래의 답변 중 하나에서 대량 삽입을 수행 할 때 데이터가 삽입 된 후 색인을 만드는 것이 더 빠르다는 것이 제안되었습니다 (색인을 먼저 작성한 다음 데이터를 삽입하는 것과 반대). 해보자:

인덱스 생성 후 데이터 삽입

sqlite3_exec(db, "CREATE  INDEX 'TTC_Stop_Index' ON 'TTC' ('Stop')", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);
...

18.13 초 만에 864913 개의 레코드를 가져 왔습니다.

데이터 삽입 후 색인 만들기

...
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "CREATE  INDEX 'TTC_Stop_Index' ON 'TTC' ('Stop')", NULL, NULL, &sErrMsg);

13.66 초에 864913 개의 레코드 가져 오기

예상대로 일괄 삽입은 한 열의 색인이 생성되면 속도가 느리지 만 데이터가 삽입 된 후 색인이 작성되면 차이가 발생합니다. 인덱스가없는 기준선은 초당 96,000 개의 인서트입니다. 먼저 인덱스를 생성 한 다음 데이터를 삽입하면 초당 47,700 개의 삽입이 발생하지만 데이터를 먼저 삽입하면 인덱스를 생성하면 초당 63,300 개의 삽입이 발생합니다.

기꺼이 다른 시나리오에 대한 제안을 시도해보고 싶습니다 ... 그리고 곧 SELECT 쿼리에 비슷한 데이터를 컴파일 할 것입니다.



독서에만 관심이 있다면 다소 속도가 빠릅니다 (오래된 데이터를 읽을 수도 있음). 버전은 여러 스레드 (스레드 당 연결)에서 여러 연결을 읽는 것입니다.

먼저 표에서 항목을 찾습니다.

 SELECT COUNT(*) FROM table

페이지 (LIMIT / OFFSET)에서 읽습니다.

  SELECT * FROM table ORDER BY _ROWID_ LIMIT <limit> OFFSET <offset>

여기서와는 다음과 같이 스레드별로 계산됩니다.

int limit = (count + n_threads - 1)/n_threads;

각 스레드에 대해 :

int offset = thread_index * limit

우리의 작은 (200MB) 데이터베이스의 경우, 50-75 %의 속도 향상 (Windows 7에서는 64 비트 3.8.0.2)이 이루어졌습니다. 우리의 테이블은 크게 비정규 화되어 있습니다 (1000-1500 컬럼, 대략 10 만개 이상의 행).

스레드가 너무 많거나 적 으면이를 수행하지 않을 것이므로 벤치마킹하고 프로필을 작성해야합니다.

또한 우리를 위해 SHAREDCACHE는 성능을 더 느리게 만들었 기 때문에 수동으로 PRIVATECACHE를 넣었습니다 (그것이 전세계에서 활성화 되었기 때문에)


몇 가지 팁 :

  1. 트랜잭션에 삽입 / 업데이트를 삽입합니다.
  2. SQLite의 이전 버전 - 덜 편집적인 저널 모드 ( pragma journal_mode )를 고려하십시오. NORMAL 이 있으며 OFF 가 있습니다. OS가 손상 될 경우 데이터베이스가 손상 될 가능성이 너무 높지 않으면 삽입 속도가 상당히 빨라질 수 있습니다. 응용 프로그램이 중단되면 데이터가 정상적으로 처리되어야합니다. 최신 버전에서는 OFF/MEMORY 설정이 응용 프로그램 수준의 충돌에 안전하지 않습니다.
  3. 페이지 크기로 재생하면 차이가 있습니다 ( PRAGMA page_size ). 페이지 크기가 커지면 큰 페이지가 메모리에 저장 될 때 읽기 및 쓰기가 조금 더 빨라집니다. 데이터베이스에 더 많은 메모리가 사용됩니다.
  4. 인덱스가있는 경우 모든 삽입 작업을 수행 한 후 CREATE INDEX 호출하는 것을 고려하십시오. 이것은 색인을 작성한 다음 삽입을 수행하는 것보다 훨씬 빠릅니다.
  5. SQLite에 대한 동시 액세스 권한이있는 경우 쓰기가 완료 될 때 전체 데이터베이스가 잠기므로 여러 독자가있을 수 있지만 쓰기가 잠길 수 있으므로 매우주의해야합니다. 이것은 새로운 SQLite 버전에서 WAL을 추가함으로써 다소 개선되었습니다.
  6. 공간 절약 ... 작은 데이터베이스를보다 빠르게 활용하십시오. 예를 들어, 키 값 쌍이있는 경우 키의 INTEGER PRIMARY KEY 를 가능하면 테이블의 암시 된 고유 한 행 번호 열을 대체합니다.
  7. 다중 스레드를 사용하는 경우 공유 페이지 캐시를 사용하여로드 된 페이지를 스레드간에 공유 할 수 있으므로 값 비싼 I / O 호출을 피할 수 있습니다.
  8. !feof(file) 사용하지 마십시오!

나는 또한 herehere 비슷한 질문을 here .


이 튜토리얼을 읽은 후에, 나는 그것을 프로그램에 구현하려고 시도했다.

주소가있는 파일이 4 ~ 5 개 있습니다. 각 파일에는 약 3 천만 개의 레코드가 있습니다. 나는 당신이 제안하고있는 것과 동일한 구성을 사용하고 있지만 초당 INSERT 수는 낮습니다 (초당 10.000 개).

여기가 당신의 제안이 실패하는 곳입니다. 모든 레코드에 대해 단일 트랜잭션을 사용하고 오류가없는 단일 삽입 / 실패가 발생합니다. 각 레코드를 다른 테이블의 여러 삽입으로 분할한다고 가정 해 보겠습니다. 기록이 깨진 경우 어떻게됩니까?

ON CONFLICT 명령이 적용되지 않습니다. 레코드에 10 개의 요소가 있고 다른 테이블에 각 요소를 삽입해야하는 경우 요소 5에 CONSTRAINT 오류가 발생하면 이전의 4 개의 삽입도 모두 이동해야합니다.

여기서 롤백이 발생합니다. 롤백의 유일한 문제점은 모든 삽입물을 잃어 버리고 위로부터 시작한다는 것입니다. 어떻게 이것을 해결할 수 있습니까?

내 솔루션은 여러 트랜잭션을 사용 하는 것이 었습니다. 나는 매 10.000 건의 레코드를 시작하고 끝낸다. (그 숫자가 왜 내가 테스트 한 것 중 가장 빠름). 10.000 크기의 배열을 만들고 거기에 성공적인 레코드를 삽입합니다. 오류가 발생하면 롤백을하고, 트랜잭션을 시작하고, 배열에서 레코드를 삽입하고, 커밋 한 다음 깨진 레코드 뒤에 새 트랜잭션을 시작합니다.

이 솔루션을 사용하면 불량 / 중복 레코드가 포함 된 파일을 처리 할 때 발생하는 문제점을 피할 수있었습니다 (거의 4 %의 불량 레코드가 있음).

내가 만든 알고리즘은 프로세스를 2 시간 단축하는 데 도움이되었습니다. 1 시간 30 분 파일의 최종로드 프로세스는 아직 느리지 만 초기에 수행 한 4 시간과 비교되지 않습니다. 나는 인서트를 10.000 / s에서 ~ 14.000 / s로 속도를 낼 수 있었다.

누군가가 속도를 높이는 방법에 대한 다른 아이디어가 있다면, 나는 제안에 개방적이다.

업데이트 :

위의 답변에 추가하여, 사용중인 하드 드라이브에 따라 초당 삽입 횟수를 명심해야합니다. 나는 다른 하드 드라이브를 가진 3 개의 다른 PC에 그것을 시험하고 시간에있는 다량 다름을 얻었다. PC1 (1 시간 30 분), PC2 (6 시간) PC3 (14 시간).

2 주 동안 연구하고 여러 리소스를 확인한 후 : 하드 드라이브, 램, 캐시, 하드 드라이브의 일부 설정이 I / O 속도에 영향을 줄 수 있음을 알았습니다. 원하는 출력 드라이브의 속성을 클릭하면 일반 탭에서 두 가지 옵션을 볼 수 있습니다. Opt1 :이 드라이브를 압축하십시오. Opt2 :이 드라이브의 파일에 내용의 색인을 허용하십시오.

이 두 가지 옵션을 비활성화하면 3 대의 PC 모두 이제 거의 동일한 시간 (1 시간 20 분 ~ 40 분)이 소요됩니다. 삽입 속도가 느린 경우 하드 드라이브가 이러한 옵션으로 구성되어 있는지 확인하십시오. 그것은 해결책을 찾으려고 많은 시간과 두통을 줄 것입니다.


INSERT / UPDATE 문을 청크 할 수 있다면 대량 가져 오기가 가장 잘 수행되는 것 같습니다. 1 만분의 1 정도의 가치가 단지 몇 줄의 테이블이있는 YMMV 테이블에서 잘 작동했습니다 ...





optimization