SQLiteの1秒あたりのINSERTパフォーマンスを向上させる


2975

SQLiteの最適化には注意が必要です。Cアプリケーションの一括挿入のパフォーマンスは、1秒あたり85挿入から1秒あたり96,000挿入を超える場合があります。

背景: SQLiteをデスクトップアプリケーションの一部として使用しています。大量の構成データがXMLファイルに格納されており、アプリケーションが初期化されるときに解析され、SQLiteデータベースにロードされてさらに処理されます。SQLiteは高速であり、特別な構成を必要とせず、データベースは単一のファイルとしてディスクに保存されるため、この状況に最適です。

理論的根拠: 最初は私が見ているパフォーマンスにがっかりしました。SQLiteのパフォーマンスは、データベースの構成方法とAPIの使用方法に応じて(一括挿入と選択の両方で)大きく異なる可能性があることがわかりました。すべてのオプションと手法が何であるかを理解することは簡単なことではなかったので、同じコミュニティのWikiエントリを作成してスタックオーバーフローリーダーと結果を共有し、同じ調査の問題を他の人に知らせるのは賢明だと思いました。

実験:一般的なパフォーマンスのヒント(つまり、「トランザクションを使用する!」)について単に話すのではなく、Cコードを記述して実際にさまざまなオプションの影響を測定するのが最善だと思いました。いくつかの簡単なデータから始めましょう:

  • トロント市の完全な輸送スケジュールの28 MBのタブ区切りテキストファイル(約865,000レコード)
  • 私のテストマシンは、Windows XPを実行する3.60 GHz P4です。
  • コードは、Visual C ++ 2005で「完全最適化」(/ Ox)付きの「リリース」としてコンパイルされ、高速コード(/ Ot)が優先されます。
  • 私はテストアプリケーションに直接コンパイルされたSQLite "Amalgamation"を使用しています。私がたまたま持っているSQLiteのバージョンは少し古い(3.6.7)ですが、これらの結果は最新のリリースに匹敵すると思われます(そうでない場合はコメントを残してください)。

コードを書いてみましょう!

コード:テキストファイルを1行ずつ読み取り、文字列を値に分割し、データを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レコードをインポート

うわぁ!2時間45分!1秒あたりの挿入数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レコードをインポート

それは良いです。すべての挿入を1つのトランザクションでラップするだけで、パフォーマンスが23,000挿入/秒に向上しました

準備済みステートメントの使用

トランザクションの使用は大幅な改善でしたが、同じSQLを何度も使用する場合、挿入ごとにSQLステートメントを再コンパイルしても意味がありません。を使用sqlite3_prepare_v2してSQLステートメントを1回コンパイルしてから、次のコマンドを使用してパラメーターをそのステートメントにバインドします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_bindingsand を呼び出すことを忘れないでくださいsqlite3_reset)が、パフォーマンスを2倍以上にして、毎秒53,000挿入になりました

PRAGMA同期=オフ

デフォルトでは、SQLiteはOSレベルの書き込みコマンドを発行した後に一時停止します。これにより、データがディスクに書き込まれることが保証されます。を設定することでsynchronous = OFF、SQLiteにデータをOSにハンドオフして書き込みを行い、続行するように指示しています。データがプラッタに書き込まれる前にコンピュータに壊滅的なクラッシュ(または電源障害)が発生すると、データベースファイルが破損する可能性があります。

/* 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);

12.41秒で864913レコードをインポート

改善点は小さくなりましたが、毎秒最大69,600挿入です

プラグマ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

前の2つの最適化を組み合わせてみましょう。(クラッシュの場合)少し危険ですが、データをインポートしているだけです(銀行を実行していません)。

/* 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レコードをインポート

素晴らしい!1秒あたり72,000回の挿入を実行できます

インメモリデータベースの使用

キックのために、以前のすべての最適化に基づいて構築し、データベースのファイル名を再定義して、完全にRAMで作業するようにします。

#define DATABASE ":memory:"

10.94秒で864913レコードをインポート

データベースをRAMに保存するのは実用的ではありませんが、1秒あたり79,000回の挿入を実行できることは印象的です

Cコードのリファクタリング

特にSQLiteの改善ではありませんchar*が、whileループ内の余分な代入演算は好きではありません。そのコードをすばやくリファクタリングして、の出力を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の次にCREATE INDEX

SELECTパフォーマンスの測定を始める前に、インデックスを作成することを知っています。以下の回答の1つで、一括挿入を行う場合、データを挿入した後にインデックスを作成する方が(最初にインデックスを作成してからデータを挿入するのではなく)速いことが示唆されています。やってみよう:

インデックスを作成してからデータを挿入する

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レコードをインポート

予想どおり、1つの列にインデックスが付けられている場合、一括挿入は遅くなりますが、データが挿入された後にインデックスが作成された場合は違いがあります。インデックスなしのベースラインは、1秒あたり96,000挿入です。最初にインデックスを作成してからデータを挿入すると、1秒あたり47,700回の挿入が行われますが、最初にデータを挿入してからインデックスを作成すると、1秒あたり63,300回の挿入が行われます。


他のシナリオについても喜んで試してみてください... SELECTクエリの同様のデータをまもなくコンパイルします。


8
いい視点ね!私たちのケースでは、XMLおよびCSVテキストファイルから200kレコードに読み込まれた約150万のキーと値のペアを扱っています。SOのようなサイトを実行するデータベースと比較すると小さいですが、SQLiteのパフォーマンスを調整することが重要になるほど十分大きいです。
Mike Willekes、2009年

51
「XMLファイルに保存された大量の構成データがあり、アプリケーションが初期化されたときにさらに処理するために解析され、SQLiteデータベースに読み込まれます。」XMLに保存して初期化時にすべてをロードするのではなく、すべてを最初にsqliteデータベースに保持しないのはなぜですか?
-CAFxX

14
呼び出さないでみましたsqlite3_clear_bindings(stmt);か?毎回十分なバインディングを設定します。最初にsqlite3_step()を呼び出す前、またはsqlite3_reset()の直後に、アプリケーションはsqlite3_bind()インターフェースの1つを呼び出してパラメーターに値をアタッチできます。sqlite3_bind()を呼び出すたびに、同じパラメーターの以前のバインディングがオーバーライドされますsqlite.org/cintro.htmlを参照)。あなたはそれを呼び出さなければならないというその関数ドキュメントには何もありません。
ahcox

21
繰り返し測定しましたか?7つのローカルポインターを回避するための4の「勝つ」は、オプティマイザが混乱していると仮定しても奇妙です。
peterchen 2012年

5
feof()入力ループの終了を制御するために使用しないでください。によって返された結果を使用しfgets()ます。stackoverflow.com/a/15485689/827263
キーストンプソン

回答:


785

いくつかのヒント:

  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)

私もここここで同様の質問をしました


9
ドキュメントはPRAGMAジャーナルモードを知らないNORMAL sqlite.org/pragma.html#pragma_journal_mode
OneWorld

4
久しぶりに、私の提案はWALが導入される前の古いバージョンに適用されました。DELETEは新しい通常の設定のようですが、OFFとMEMORYの設定も追加されました。OFF / MEMORYはデータベースの整合性を犠牲にして書き込みパフォーマンスを改善すると思いますが、OFFはロールバックを完全に無効にします。
Snazzer 2014年

4
#7の場合、c#system.data.sqliteラッパーを使用して共有ページキャッシュを有効にする方法の例はありますか?
アーロンフドン

4
#4は昔の記憶を取り戻しました-以前に少なくとも1つのケースがあり、追加グループの前にインデックスを削除し、その後に挿入を大幅に高速化して再作成しました。期間中、テーブルに単独でアクセスできることがわかっている場合は、最新のシステムでより迅速に機能する可能性があります。
Bill K

#1の賛成:自分でトランザクションをうまく処理できました。
Enno

146

これらのインサートのSQLITE_STATIC代わりに使用してみてくださいSQLITE_TRANSIENT

SQLITE_TRANSIENT SQLiteは、返す前に文字列データをコピーします。

SQLITE_STATIC与えられたメモリアドレスは、クエリが実行されるまで有効であることを伝えます(このループでは常にそうです)。これにより、ループごとに複数の割り当て、コピー、割り当て解除の操作を節約できます。おそらく大幅な改善。


109

避けてくださいsqlite3_clear_bindings(stmt)

テストのコードは毎回バインディングを設定しますが、それで十分です。

SQLiteドキュメントからのC APIのイントロは言う:

sqlite3_step()を初めて呼び出す前、またはsqlite3_reset()の直後に、アプリケーションはsqlite3_bind()インターフェースを呼び出し て、パラメーターに値をアタッチできます。sqlite3_bind()を呼び出すたびに、同じパラメーターの以前のバインディングがオーバーライドされます

のドキュメントには何もありません sqlite3_clear_bindings単にバインディングを設定するだけでなく、それを呼び出さなければならないと言っていること。

詳細:回避_sqlite3_clear_bindings()


5
驚くほど正しい:「多くの直観に反して、sqlite3_reset()は準備済みステートメントのバインディングをリセットしません。このルーチンを使用して、すべてのホストパラメータをNULLにリセットします。」- sqlite.org/c3ref/clear_bindings.html
フランシスStraccia

63

一括挿入について

この投稿とここで私を導いたスタックオーバーフローの質問に触発されました-SQLiteデータベースに一度に複数の行を挿入することは可能ですか?-私は最初のGitリポジトリを投稿しました:

https://github.com/rdpoor/CreateOrUpdate

MySQL、SQLite、またはPostgreSQLにActiveRecordの配列を一括でロードするものデータベースます。既存のレコードを無視する、それらを上書きする、またはエラーを発生させるオプションが含まれています。私の初歩的なベンチマークは、シーケンシャル書き込みと比較して10倍の速度の向上を示しています-YMMV。

大規模なデータセットを頻繁にインポートする必要がある本番用コードで使用しており、非常に満足しています。


4
@ジェス:リンクをクリックすると、バッチ挿入構文を意味していることがわかります。
Alix Axel

48

一括インポートは、INSERT / UPDATEステートメントをチャンクできる場合に最もパフォーマンスが向上するようです。10,000行程度の値で、行数が少ないテーブル、YMMV ...


22
x = 10,000に調整して、x =キャッシュ[= cache_size * page_size] /挿入の平均サイズになるようにします。
Alix Axel

43

読み取りのみに関心がある場合、いくらか高速な(ただし古いデータを読み取る可能性がある)バージョンは、複数のスレッドからの複数の接続から読み取る(スレッドごとの接続)です。

まず、表でアイテムを見つけます。

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)dbの場合、これにより50-75%スピードアップしました(Windows 7では3.8.0.2 64ビット)。私たちのテーブルはかなり正規化されていません(1000-1500列、およそ100,000以上の行)。

スレッドが多すぎたり少なすぎたりするとうまくいきません。自分でベンチマークとプロファイルを行う必要があります。

また、SHAREDCACHEを使用するとパフォーマンスが低下したため、PRIVATECACHEを手動で配置しました(グローバルに有効にしたため)。


29

cache_sizeをより高い値に上げるまで、トランザクションから利益を得ることはできませんでした。 PRAGMA cache_size=10000;


に正の値を使用すると、RAMの合計サイズではなく、キャッシュするページ数がcache_size設定されます。4kBのデフォルトのページサイズでは、この設定は開いているファイルごと(または共有キャッシュで実行している場合はプロセスごと)に最大40MBのデータを保持します。
Groo

21

このチュートリアルを読んだ後、プログラムに実装してみました。

アドレスを含む4〜5個のファイルがあります。各ファイルには約3000万のレコードがあります。私はあなたが提案しているのと同じ構成を使用していますが、1秒あたりのINSERTの数は非常に少なくなっています(1秒あたり約10.000レコード)。

ここで提案が失敗します。すべてのレコードに対して単一のトランザクションを使用し、エラー/失敗のない単一の挿入を使用します。各レコードを異なるテーブルの複数の挿入に分割するとします。レコードが壊れた場合はどうなりますか?

ON CONFLICTコマンドは適用されません。レコードに10の要素があり、各要素を異なるテーブルに挿入する必要がある場合、要素5がCONSTRAINTエラーを受け取る場合、以前の4つの挿入もすべて実行する必要があります。

そこで、ここでロールバックが行われます。ロールバックの唯一の問題は、すべての挿入が失われ、先頭から開始することです。どうすればこれを解決できますか?

私の解決策は、複数のトランザクションを使用することでした。私は10.000レコードごとにトランザクションを開始および終了します(なぜその数をテストしないでください。それが私がテストした最速のレコードでした)。10.000サイズの配列を作成し、そこに成功したレコードを挿入しました。エラーが発生したら、ロールバックを実行してトランザクションを開始し、配列からレコードを挿入してコミットし、壊れたレコードの後に​​新しいトランザクションを開始します。

このソリューションは、不良/重複レコードを含むファイルを処理するときに発生する問題を回避するのに役立ちました(不良レコードがほぼ4%ありました)。

作成したアルゴリズムは、プロセスを2時間短縮するのに役立ちました。ファイルの最終読み込みプロセスは1時間30分ですが、それでもまだ低速ですが、最初にかかった4時間と比較されていません。挿入を10.000 / sから〜14.000 / sまで高速化できました

それをスピードアップする方法について他のアイデアがある場合は、私は提案を受け入れます。

更新

上記の私の回答に加えて、1秒あたりの挿入数は、使用しているハードドライブによっても異なることに注意してください。ハードドライブが異なる3台のPCでテストしたところ、時間に大きな違いがありました。PC1(1時間30分)、PC2(6時間)PC3(14時間)なので、なぜそうなのかと思い始めました。

2週間の調査と複数のリソース(ハードドライブ、Ram、キャッシュ)のチェックの結果、ハードドライブの一部の設定がI / O速度に影響を与えることがわかりました。目的の出力ドライブのプロパティをクリックすると、[全般]タブに2つのオプションが表示されます。Opt1:このドライブを圧縮し、Opt2:このドライブのファイルにコンテンツのインデックスを作成できるようにします。

これら2つのオプションを無効にすることで、3台すべてのPCが終了するまでにほぼ同じ時間がかかります(1時間20〜40分)。挿入が遅い場合は、ハードドライブがこれらのオプションで構成されているかどうかを確認してください。それはあなたに多くの時間と解決策を見つけることを試みる頭痛を節約します


以下を提案します。* SQLITE_STATICとSQLITE_TRANSIENTを使用して文字列のコピーを回避し、トランザクションが実行される前に文字列が変更されないようにする必要があります*一括挿入INSERT INTO stop_times VALUES(NULL、?、?、?、?、?、?、?、? 、?)、(NULL、?、?、?、?、?、?、?、?、?)、(NULL、?、?、?、?、?、?、?、?、?)、(NULL 、?、?、?、?、?、?、?、?、?)、(NULL、?、?、?、?、?、?、?、?、?)*ファイルをmmapして、 syscalls。
rouzier

そうすることで、5,582,642レコードを11.51秒でインポートできます
rouzier

11

あなたの質問への答えは、新しいSQLite 3はパフォーマンスが向上しているということです、それを使用してください。

この回答なぜ、SQLiteを使用したSQLAlchemyの挿入は、sqlite3を直接使用するよりも25倍遅いのですか?SqlAlchemyによるOrm Authorは0.5秒で100kの挿入を行い、python-sqliteとSqlAlchemyで同様の結果を確認しました。これにより、SQLite 3でパフォーマンスが向上したと思います。


-1

ContentProviderを使用して、バルクデータをdbに挿入します。以下の方法は、データベースにバルクデータを挿入するために使用されます。これにより、SQLiteの1秒あたりのINSERTパフォーマンスが向上します。

private SQLiteDatabase database;
database = dbHelper.getWritableDatabase();

public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {

database.beginTransaction();

for (ContentValues value : values)
 db.insert("TABLE_NAME", null, value);

database.setTransactionSuccessful();
database.endTransaction();

}

bulkInsertメソッドを呼び出します。

App.getAppContext().getContentResolver().bulkInsert(contentUriTable,
            contentValuesArray);

リンク:https : //www.vogella.com/tutorials/AndroidSQLite/article.html詳細については、ContentProviderセクションの使用を確認してください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.