web-dev-qa-db-ja.com

SQLite:大きなテーブルでCOUNTが遅い

SQLiteで、大きなテーブルにSELECT COUNT(*)を使用するとパフォーマンスの問題が発生します。

まだ有効な回答が得られず、さらにいくつかのテストを行ったので、新しい調査結果を組み込むために質問を編集しました。

2つのテーブルがあります。

CREATE TABLE Table1 (
Key INTEGER NOT NULL,
... several other fields ...,
Status CHAR(1) NOT NULL,
Selection VARCHAR NULL,
CONSTRAINT PK_Table1 PRIMARY KEY (Key ASC))

CREATE Table2 (
Key INTEGER NOT NULL,
Key2 INTEGER NOT NULL,
... a few other fields ...,
CONSTRAINT PK_Table2 PRIMARY KEY (Key ASC, Key2 ASC))

Table1には約800万のレコードがあり、Table2には約5100万のレコードがあり、データベースファイルは5GBを超えています。

Table1には、さらに2つのインデックスがあります。

CREATE INDEX IDX_Table1_Status ON Table1 (Status ASC, Key ASC)
CREATE INDEX IDX_Table1_Selection ON Table1 (Selection ASC, Key ASC)

「ステータス」は必須フィールドですが、6つの異なる値しかありません。「選択」は必須ではなく、nullと異なる約150万の値と約600kの異なる値しかありません。

両方のテーブルでいくつかのテストを行いました。以下のタイミングを確認でき、各リクエスト(QP)に「クエリプランの説明」を追加しました。 USBメモリスティックにデータベースファイルを配置したので、各テスト後にデータベースファイルを削除して、ディスクキャッシュに干渉することなく信頼できる結果を得ることができました。一部のリクエストはUSBでより高速です(シークタイムがないためと考えられます)が、一部は遅い(テーブルスキャン)。

SELECT COUNT(*) FROM Table1
    Time: 105 sec
    QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Selection(~1000000 rows)
SELECT COUNT(Key) FROM Table1
    Time: 153 sec
    QP: SCAN TABLE Table1 (~1000000 rows)
SELECT * FROM Table1 WHERE Key = 5123456
    Time: 5 ms
    QP: SEARCH TABLE Table1 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1
    Time: 16 sec
    QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Status (Status=?) (~3 rows)
SELECT * FROM Table1 WHERE Selection = 'SomeValue' AND Key > 5123456 LIMIT 1
    Time: 9 ms
    QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Selection (Selection=?) (~3 rows)

ご覧のとおり、カウントは非常に遅いですが、通常の選択は高速です(2番目の選択は16秒かかりました)。

同じことがTable2にも当てはまります。

SELECT COUNT(*) FROM Table2
    Time: 528 sec
    QP: SCAN TABLE Table2 USING COVERING INDEX sqlite_autoindex_Table2_1(~1000000 rows)
SELECT COUNT(Key) FROM Table2
    Time: 249 sec
    QP: SCAN TABLE Table2 (~1000000 rows)
SELECT * FROM Table2 WHERE Key = 5123456 AND Key2 = 0
    Time: 7 ms
    QP: SEARCH TABLE Table2 USING INDEX sqlite_autoindex_Table2_1 (Key=? AND Key2=?) (~1 rows)

SQLiteがTable1の主キーで自動的に作成されたインデックスを使用しないのはなぜですか?そして、なぜ、彼がTable2で自動インデックスを使用する場合でも、それでも多くの時間がかかりますか?

SQL Server 2008 R2で同じコンテンツとインデックスを使用して同じテーブルを作成したところ、カウントはほぼ瞬時になりました。

以下のコメントの1つは、データベースでANALYZEを実行することを提案しています。完了しました。完了するまでに11分かかりました。その後、いくつかのテストを再度実行しました。

SELECT COUNT(*) FROM Table1
    Time: 104 sec
    QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Selection(~7848023 rows)
SELECT COUNT(Key) FROM Table1
    Time: 151 sec
    QP: SCAN TABLE Table1 (~7848023 rows)
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1
    Time: 5 ms
    QP: SEARCH TABLE Table1 USING INTEGER PRIMARY KEY (rowid>?) (~196200 rows)
SELECT COUNT(*) FROM Table2
    Time: 529 sec
    QP: SCAN TABLE Table2 USING COVERING INDEX sqlite_autoindex_Table2_1(~51152542 rows)
SELECT COUNT(Key) FROM Table2
    Time: 249 sec
    QP: SCAN TABLE Table2 (~51152542 rows)

ご覧のとおり、クエリには同じ時間がかかりました(ただし、クエリプランが実際の行数を表示している場合を除く)。遅い選択のみが高速になります。

次に、Table1のKeyフィールドに自動インデックスに対応する追加のインデックスを作成します。 ANALYZEデータなしで、元のデータベースでこれを行いました。このインデックスの作成には23分以上かかりました(これはUSBスティック上にあることを思い出してください)。

CREATE INDEX IDX_Table1_Key ON Table1 (Key ASC)

次に、テストを再度実行しました。

SELECT COUNT(*) FROM Table1
    Time: 4 sec
    QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Key(~1000000 rows)
SELECT COUNT(Key) FROM Table1
    Time: 167 sec
    QP: SCAN TABLE Table2 (~1000000 rows)
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1
    Time: 17 sec
    QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Status (Status=?) (~3 rows)

ご覧のとおり、インデックスはcount(*)には役立ちましたが、count(Key)には役立ちませんでした。

最後に、テーブル制約の代わりに列制約を使用してテーブルを作成しました。

CREATE TABLE Table1 (
Key INTEGER PRIMARY KEY ASC NOT NULL,
... several other fields ...,
Status CHAR(1) NOT NULL,
Selection VARCHAR NULL)

次に、テストを再度実行しました。

SELECT COUNT(*) FROM Table1
    Time: 6 sec
    QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Selection(~1000000 rows)
SELECT COUNT(Key) FROM Table1
    Time: 28 sec
    QP: SCAN TABLE Table1 (~1000000 rows)
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1
    Time: 10 sec
    QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Status (Status=?) (~3 rows)

クエリプランは同じですが、時間が大幅に短縮されます。どうしてこれなの ?

問題は、ALTER TABLEが既存のテーブルの変換を許可しておらず、このフォームに変換できない既存のデータベースがたくさんあることです。さらに、テーブル制約の代わりに列制約を使用しても、Table2では機能しません。

誰かが私が間違っていることとこの問題を解決する方法を知っていますか?

System.Data.SQLiteバージョン1.0.74.0を使用してテーブルを作成し、SQLiteSpy 1.9.1を使用してテストを実行しました。

おかげで、

マーク

31
Marc

DELETEdレコードがない場合は、次のようにします。

SELECT MAX(_ROWID_) FROM "table" LIMIT 1;

全表スキャンを回避します。ご了承ください - _ROWID_はSQLite識別子です

25
Alix Axel

から http://old.nabble.com/count(*)-slow-td869876.html

SQLiteは常にcount(*)の全表スキャンを実行します。それ
これを高速化するためにテーブルにメタ情報を保持しません
処理します。

メタ情報を保持しないことは意図的な設計です
決定。各テーブルにカウント(またはそれ以上)が格納されている場合
btreeのノードはカウントを保存しました)その後、さらに更新します
すべてのINSERTまたはDELETEで発生する必要があります。この
一般的な場合でも、INSERTとDELETEが遅くなります
count(*)速度が重要ではない場合。

高速なCOUNTが本当に必要な場合は、
実行中を更新するINSERTおよびDELETEのトリガー
個別のテーブルでカウントしてから、その個別のクエリを実行します
最新のカウントを見つけるためのテーブル。

もちろん、もしあなたが完全な行数を維持する価値はありません
WHERE句に依存するCOUNTが必要です(つまり、WHERE field1> 0およびfield2 <1000000000)。

24
user78706

星を数えないでください、記録を数えてください!または他の言語では、決して発行しない

SELECT COUNT(*)FROM tablename;

使用する

SELECT COUNT(ROWID)FROM tablename;

違いを確認するには、両方のEXPLAIN QUERY PLANを呼び出します。 WHERE句で言及されているすべての列を含むインデックスが配置されていることを確認してください。

2
Thinkeye

これはあまり役に立ちませんが、 [〜#〜] analyze [〜#〜] コマンドを実行してデータベースに関する統計を再構築できます。 「ANALYZE; "を使用してデータベース全体の統計を再構築し、クエリを再度実行して、より高速かどうかを確認します。

1

列の制約に関して、SQLiteはINTEGER PRIMARY KEYとして宣言された列を内部の行IDにマッピングします(これにより、内部の最適化がいくつか許可されます)。理論的には、個別に宣言された主キー制約に対しても同じことができますが、少なくともSQLiteのバージョンが使用されている場合、実際にはそうではないようです。 (System.Data.SQLite 1.0.74.0はコアSQLite 3.7.7.1。に対応します。1.0.79.0で数値を再確認することをお勧めします。これを行うためにデータベースを変更する必要はなく、ライブラリのみです。)

0
Donal Fellows

クエリのパフォーマンスを向上させるための潜在的な回避策を次に示します。コンテキストからすると、クエリの実行に約1分半かかるようです。

Date_created列がある(または列を追加できる)と仮定して、毎日午前0時(たとえば00:05 am)にバックグラウンドでクエリを実行し、計算されたlast_updatedの日付とともに値をどこかに永続化します(それは少しです)。

次に、date_created列(インデックス付き)に対して実行すると、SELECT COUNT(*)FROM TABLE WHERE date_updated> "[TODAY] 00:00:05"のようなクエリを実行することで、テーブル全体のスキャンを回避できます。

そのクエリのカウント値を永続化された値に追加すると、一般的に正確なかなり高速なカウントになります。

唯一の問題は、午前12時5分から午前12時7分(合計カウントクエリが実行されている期間)まで、テーブル全体のスキャンcount()のlast_updated値を確認できる競合状態にあることです。 24時間以上経過している場合、増分カウントクエリは、1日のカウントと今日の経過時間を取得する必要があります。経過時間が24時間未満の場合、増分カウントクエリは、1日の部分的なカウント(今日の経過時間のみ)を取得する必要があります。

0
user1214836

私は同じ問題を抱えていましたが、私の状況ではVACUUMコマンドが役立ちました。データベースCOUNT(*)で実行した後、速度はほぼ100倍に向上しました。ただし、コマンド自体は私のデータベース(2000万レコード)で数分を必要とします。メインウィンドウの破棄後にソフトウェアが終了するときにVACUUMを実行することでこの問題を解決しました。そのため、遅延によってユーザーに問題が発生することはありません。

0
Vitalii

高速クエリの出力はすべて「QP:SEARCH」というテキストで始まります。遅いクエリのテキストは「QP:SCAN」というテキストで始まりますが、sqliteがカウントを生成するためにテーブル全体のスキャンを実行していることを示唆しています。

"sqlite table scan count"をグーグル検索すると the following が見つかります。これは、フルテーブルスキャンを使用してカウントを取得することがsqliteの動作方法であり、おそらく避けられないことを示唆しています。

回避策として、そのステータスには8つの値しかないので、次のようなクエリを使用してすばやくカウントを取得できるかどうか疑問に思いましたか?

select 1 where status = 1 union select 1 where status = 2 ...

次に、結果の行をカウントします。これは明らかに醜いですが、クエリではなくスキャンとしてクエリを実行するようにsqliteを説得すれば、うまくいくかもしれません。毎回「1」を返すのは、実際のデータを返すオーバーヘッドを回避するためです。

0
Matt T