web-dev-qa-db-ja.com

MySQLでの行カウントの高速化

説明のために、3つの列を持つ単純なMySQL "books"テーブルを使用してライブラリを実行しているとします。

(ID、タイトル、ステータス)

  • idは主キーです
  • titleは本のタイトルです
  • statusは、書籍の現在の状態を説明する列挙型である可能性があります(例:AVAILABLE、CHECKEDOUT、PROCESSING、MISSING)

各州に該当する本の数を報告する簡単なクエリは次のとおりです。

SELECT status, COUNT(*) FROM books GROUP BY status

または、利用可能な本の数を具体的に見つけるには:

SELECT COUNT(*) FROM books WHERE status = "AVAILABLE"

ただし、テーブルが数百万行になると、これらのクエリは完了するまでに数秒かかります。 「ステータス」列にインデックスを追加しても、私の経験に違いはないようです。

定期的に結果をキャッシュしたり、(トリガーやその他のメカニズムを介して)ブックの状態が変わるたびに別のテーブルに要約情報を明示的に更新したりする以外に、これらの種類のクエリを高速化する手法はありますか? COUNTクエリはすべての行を調べてしまうようですが、(詳細がわからないため)この情報がインデックスからなんらかの形で判断できないことに少し驚いています。

[〜#〜]更新[〜#〜]

200万行のサンプルテーブル(インデックス付きの「ステータス」列を含む)を使用して、GROUP BYクエリのベンチマークを行いました。 InnoDBストレージエンジンを使用すると、私のマシンではクエリに3.0〜3.2秒かかります。 MyISAMを使用すると、クエリに0.9〜1.1秒かかります。どちらの場合でも、count(*)、count(status)、またはcount(1)の間に有意差はありませんでした。

MyISAMは確かに少し高速ですが、同等のクエリをはるかに高速化する方法があるかどうか知りたいと思っていました(例えば10-50 ms) -キャッシュとトリガーの精神的なオーバーヘッドなしで、トラフィックの少ないサイトのすべてのWebページ要求で呼び出されるのに十分な速さ)。答えは「直接クエリをすばやく実行する方法がない」というように思えます。これは私が期待していたことです。簡単な代替策がないことを確認したかっただけです。

41
Kevin Ivarsen

だから問題は

これらの種類のクエリを高速化するためのテクニックはありますか?

まあ、そうでもない。列ベースのストレージエンジンは、これらのSELECT COUNT(*)クエリを使用するとおそらく高速になりますが、他のほとんどのクエリではパフォーマンスが低下します。

あなたの最善の策は、トリガーを介して要約テーブルを維持することです。オーバーヘッドはあまりなく、テーブルがどれほど大きくてもSELECT部分​​は瞬時に実行されます。これが定型コードです。

DELIMITER //

CREATE TRIGGER ai_books AFTER INSERT ON books
FOR EACH ROW UPDATE books_cnt SET total = total + 1 WHERE status = NEW.status
//
CREATE TRIGGER ad_books AFTER DELETE ON books
FOR EACH ROW UPDATE books_cnt SET total = total - 1 WHERE status = OLD.status;
//
CREATE TRIGGER au_books AFTER UPDATE ON books
FOR EACH ROW
BEGIN
    IF (OLD.status <> NEW.status)
    THEN
        UPDATE books_cnt SET total = total + IF(status = NEW.status, 1, -1) WHERE status IN (OLD.status, NEW.status);
    END IF;
END
//
38
Josh Davis

MyISAMは、count(*)で実際にはかなり高速ですが、MyISAMストレージは信頼性が低く、データの整合性が重要な場合は回避するのが最善です。

InnoDBは、count(*)タイプのクエリの実行が非常に遅くなる可能性があります。これは、同じデータの複数の同時ビューを可能にするように設計されているためです。したがって、どの時点でも、カウントを取得するためにインデックスにアクセスするだけでは不十分です。

差出人: http://www.mail-archive.com/[email protected]/msg120320.html

データベースは1000個のレコードから始まります。トランザクションを開始します。トランザクションを開始します。50レコードを削除します。50レコードを追加します。COUNT()を実行して、950レコードを表示します。 COUNT()を実行すると、1050レコードが表示されます。私はトランザクションをコミットします-データベースには、あなた以外のすべての人に950のレコードが含まれています。トランザクションをコミットします-データベースには再び1000レコードがあります。

トランザクションに関してどのレコードが「可視」または「変更可能」であるかについてInnoDBがどのように対応するかは、行レベルのロック、トランザクション分離レベル、およびマルチバージョン化によって行われます。 http://dev.mysql.com/doc/refman/4.1/en/innodb-transaction-model.htmlhttp://dev.mysql.com/doc/refman/ 4.1/en/innodb-multi-versioning.html

それが、各人が見ることができるレコードの数を数えることをそれほど簡単ではありません。

つまり、この情報を頻繁かつ高速に取得する必要がある場合は、テーブルに移動するのではなく、何らかの方法でカウントをキャッシュすることを検討する必要があります。

9
Sam Saffron

から: http://dev.mysql.com/doc/refman/5.0/en/innodb-restrictions.html

InnoDBはテーブル内の行の内部カウントを保持しません。 (実際には、これはマルチバージョン管理のために多少複雑になります。)SELECT COUNT(*)FROM tステートメントを処理するには、InnoDBがテーブルのインデックスをスキャンする必要があります。インデックスが完全にバッファにない場合は時間がかかります。プール。

推奨されるソリューションは次のとおりです。

高速なカウントを取得するには、自分で作成したカウンターテーブルを使用し、アプリケーションが挿入と削除に従ってそれを更新できるようにする必要があります。おおよその行数で十分な場合は、SHOW TABLE STATUSも使用できます。

つまり、count(*)(innoDBの場合)は、多数の行を含むテーブルでは長い時間がかかります。これは仕様によるもので、仕方がありません。

独自の回避策を記述します。

8
Alterlife

Count(*)、count(status)、またはcount(1)の間に有意差はありませんでした

count(column)は、columnがNOT NULLである行の数を返します。 1はNOT NULLであり、ステータスもおそらくNOT NULLであるため、データベースはテストを最適化し、それらをすべてcount(*)に変換します。皮肉なことに、これは「すべての列がnullでない行のカウント」(または他の組み合わせ)を意味するのではなく、単に「行のカウント」を意味します...

さて、あなたの質問に戻って、あなたはあなたのケーキを持ってそれを食べることができません...

  • 「正確な」カウントを常に利用できるようにしたい場合は、トリガーを介してリアルタイムで増分および減分する必要があります。これにより、書き込みが遅くなります。

  • または、count(*)を使用できますが、これは遅くなります

  • または、大まかな見積もりまたは古い値を決定し、キャッシングまたは他の確率論的アプローチを使用できます。

一般に、「数個」以上の値では、NO-ONEは正確なリアルタイムカウントに関心があります。とにかくそれは赤いニシンです。あなたがそれを読んだときまでに、値はおそらく変更されているでしょう。

4
peufeu

ここでの多くの回答は、インデックスは役に立たないと言っていましたが、私の場合は役に立ちました...

私のテーブルはMyISAMを使用し、約10万行しかありませんでした。クエリ:

select count(*) from mytable where foreign_key_id=n

完了するまでに7〜8秒かかりました。

foreign_key_idにインデックスを追加しました:

create index myindex on mytable (foreign_key_id) using btree;

インデックスを作成した後、上のselectステートメントは0.00秒の実行時間を報告しました。

3
Witt