アプリケーションでSQLite3によるメモリ消費を削減する方法を探しています。
実行するたびに、次のスキーマでテーブルが作成されます。
(main TEXT NOT NULL PRIMARY KEY UNIQUE,count INTEGER DEFAULT 0)
その後、データベースは1秒あたり5万回の操作でいっぱいになります。書き込みのみ。
アイテムがすでに存在する場合、更新クエリを使用して「カウント」を更新します(これはUPSERTと呼ばれると思います)。これらは私の質問です:
INSERT OR IGNORE INTO table (main) VALUES (@SEQ);
UPDATE tables SET count=count+1 WHERE main = @SEQ;
このように、トランザクションごとに500万回の操作で、DBに非常に高速に書き込むことができます。
この問題のディスク容量はあまり気にしませんが、RAMの容量には非常に制約があります。したがって、メモリを浪費することはできません。
Sqlite3_user_memory()を使用すると、実行中にメモリ消費量がほぼ3GBに増加することが通知されます。 sqlite3_soft_heap_limit64()を使用して2GBに制限すると、2GBに達すると、データベース操作のパフォーマンスがほぼゼロに低下します。
望ましいパフォーマンスを実現するには、キャッシュサイズを1M(ページサイズがデフォルト)に増やす必要がありました。
メモリ消費を減らすために何ができますか?
私は...するだろう:
PRAGMA locking_mode = EXCLUSIVE;
を使用してくださいまた、(ご存知かどうかはわかりませんが)PRAGMA cache_size
はMB単位ではなく、ページ単位です。ターゲットメモリをPRAGMA cache_size * PRAGMA page_size
として定義するか、SQLite> = 3.7.10でPRAGMA cache_size = -kibibytes;
を実行することもできます。 1 M(illion)に設定すると、1GBまたは2GBになります。
cache_size
がINSERTでどのように役立つのか興味があります...
PRAGMA temp_store = FILE;
が違いを生む場合は、ベンチマークを試すこともできます。
そしてもちろん、データベースが書き込まれていないときはいつでも:
PRAGMA shrink_memory;
VACUUM;
データベースで何をしているかによっては、次のことも役立つ場合があります。
PRAGMA auto_vacuum = 1|2;
PRAGMA secure_delete = ON;
次のプラグマを使用していくつかのテストを実行しました。
busy_timeout=0;
cache_size=8192;
encoding="UTF-8";
foreign_keys=ON;
journal_mode=WAL;
legacy_file_format=OFF;
synchronous=NORMAL;
temp_store=MEMORY;
INSERT OR IGNORE INTO test (time) VALUES (?);
UPDATE test SET count = count + 1 WHERE time = ?;
ピーク時の1秒あたりの更新数は約109kです。
REPLACE INTO test (time, count) VALUES
(?, coalesce((SELECT count FROM test WHERE time = ? LIMIT 1) + 1, 1));
1秒あたり最大12万回の更新でピークに達しました。
また、PRAGMA temp_store = FILE;
を試しましたが、更新は1秒あたり約1〜2k減少しました。
トランザクションでの7Mの更新の場合、journal_mode=WAL
は他のすべての更新よりも遅くなります。
データベースに35,839,987レコードを入力しましたが、セットアップには65521更新のバッチごとに約4秒かかりますが、16MBのメモリ消費にも達していません。
わかりました、ここにもう1つあります:
INTEGER PRIMARY KEY列のインデックス(実行しないでください)
INTEGER PRIMARY KEYを使用して列を作成すると、SQLiteはこの列をテーブル構造のキー(インデックス)として使用します。これは、この列の非表示のインデックスです(SQLite_Masterテーブルには表示されないため)。列に別のインデックスを追加する必要はなく、使用されることはありません。さらに、INSERT、DELETE、およびUPDATE操作の速度が低下します。
PKをNOTNULL + UNIQUEとして定義しているようです。 PKは暗黙的にUNIQUEです。
メモリ消費量が多いのは、1つの大きなトランザクションに集中している操作が多すぎることが原因のようです。 100万回の操作のように、より小さなトランザクションをコミットしようとすると役立つ場合があります。トランザクションあたり500万回の操作は、多くのメモリを消費します。
ただし、操作速度とメモリ使用量のバランスを取ります。
小さいトランザクションは役に立たないので、_PRAGMA shrink_memory
_が選択かもしれません。
sqlite3_status()
と_SQLITE_STATUS_MEMORY_USED
_を使用して、動的メモリ割り当てをトレースし、ポイントを特定します。
1つのトランザクションのすべての操作がテーブル全体に分散されているため、テーブルのすべてのページにアクセスする必要があるとすると、ワーキングセットのサイズは次のようになります。
main
列のインデックスに約1GB、さらにcount
列を別のテーブルに移動することで、操作ごとに変更されるデータの量を減らすことができます。
CREATE TABLE main_lookup(main TEXT NOT NULL UNIQUE, rowid INTEGER PRIMARY KEY);
CREATE TABLE counters(rowid INTEGER PRIMARY KEY, count INTEGER DEFAULT 0);
次に、各操作について:
SELECT rowid FROM main_lookup WHERE main = @SEQ;
if not exists:
INSERT INTO main_lookup(main) VALUES(@SEQ);
--read the inserted rowid
INSERT INTO counters VALUES(@rowid, 0);
UPDATE counters SET count=count+1 WHERE rowid = @rowid;
Cでは、挿入されたrowid
は sqlite3_last_insert_rowid で読み取られます。
SELECT
とINSERT
を別々に実行することは、INSERT OR IGNORE
よりも遅くはありません。 SQLiteはどちらの場合も同じ作業を行います。
この最適化は、ほとんどの操作が既存のカウンターを更新する場合にのみ役立ちます。
ブレーンストーミングの精神で、私は答えを冒険します。私はこの仲間のようなテストをしていません:
SQLiteの1秒あたりのINSERTのパフォーマンスを向上させますか?
私の仮説は、テキストの主キーのインデックスは、2つの整数列のいくつかのインデックス(ハッシュテーブルをシミュレートするために必要なもの)よりもRAMを集中的に使用する可能性があるというものです。
編集:実際には、これには主キーさえ必要ありません:
create table foo( slot integer, myval text, occurrences int);
create index ix_foo on foo(slot); // not a unique index
整数の主キー(またはスロットの一意でないインデックス)では、テキスト値がすでにファイルにあるかどうかをすばやく判断する方法がありません。したがって、その要件に対処するために、ハッシュキーをシミュレートして、別の投稿者に提案したものを実装してみてください。
ハッシュキー関数を使用すると、テキスト値が存在する場合に格納される場所を決定できます。
http://www.cs.princeton.edu/courses/archive/fall08/cos521/hash.pdfhttp://www.fearme.com/misc/alg/node28。 htmlhttp://cs.mwsu.edu/~griffin/courses/2133/downloads/Spring11/p677-pearson.pdf