_group by
_でselect count(*)
を高速化する方法は?
遅すぎて、非常に頻繁に使用されます。
3,000,000行を超えるテーブルでselect count(*)
と_group by
_を使用すると大きな問題が発生します。
_select object_title,count(*) as hot_num
from relations
where relation_title='XXXX'
group by object_title
_
relation_title、object_titleはvarcharです。 relation_title = 'XXXX'は、1,000,000行を超える行を返し、object_titleのインデックスにつながります。うまく機能しませんでした。
難易度の高い順に、いくつか試してみます。
(easier)-適切なカバーインデックスがあることを確認してください
CREATE INDEX ix_temp ON relations (relation_title, object_title);
これにより、既存のスキーマが与えられた場合にパフォーマンスが最大化されます。これは、(mySQLのオプティマイザのバージョンが本当にダムでない限り)クエリを満たすために必要なI/Oの量が最小化されるためです(インデックスが逆の順序でインデックス全体が逆の場合とは異なります)。スキャンする必要があります)、それはクエリをカバーするので、クラスター化されたインデックスに触れる必要はありません。
(少し難しい)-varcharフィールドができるだけ小さいことを確認してください
MySQLのvarcharインデックスに関するパフォーマンスの課題の1つは、クエリを処理するときに、フィールドの宣言されたサイズ全体がRAMにプルされることです。したがって、varchar(256)があり、4文字しか使用していない場合でも、クエリの処理中に256バイトのRAM使用量を支払うことになります。痛い!縮小できる場合varcharは簡単に制限されます。これにより、クエリが高速化されます。
(難しい)-正規化
単一の文字列値を持つ行の30%は、別のテーブルに正規化することを明確に求めているため、文字列を何百万回も複製することはありません。 3つのテーブルに正規化し、整数IDを使用してそれらを結合することを検討してください。
場合によっては、内部で正規化し、現在のテーブルの名前と一致するビューで正規化を非表示にすることができます...その後、INSERT/UPDATE/DELETEクエリに正規化を認識させるだけで、SELECTをそのままにしておくことができます。
(最も難しい)-文字列列をハッシュし、ハッシュにインデックスを付けます
正規化とはコードの変更が多すぎることを意味しますが、スキーマを少し変更できる場合は、文字列列に128ビットのハッシュを作成することを検討してください( MD5関数 を使用)。この場合(正規化とは異なり)、すべてのクエリを変更する必要はなく、INSERTと一部のSELECTのみを変更する必要があります。とにかく、文字列フィールドをハッシュしてから、ハッシュにインデックスを作成する必要があります。
CREATE INDEX ix_temp ON relations (relation_title_hash, object_title_hash);
SELECTを試して、クラスター化インデックスを取得せずにハッシュインデックスを介して計算を実行していることを確認する必要があることに注意してください(クエリを満たすためにobject_titleの実際のテキスト値を解決する必要があります)。
また、relation_titleのvarcharサイズが小さいが、オブジェクトtitleのサイズが長い場合は、object_titleのみをハッシュして、(relation_title, object_title_hash)
にインデックスを作成できる可能性があります。
このソリューションは、これらのフィールドの一方または両方がハッシュのサイズに比べて非常に長い場合にのみ役立つことに注意してください。
また、小文字の文字列のハッシュは大文字のハッシュと同じではないため、ハッシュによる大文字と小文字の区別/照合の影響が興味深いことにも注意してください。したがって、文字列をハッシュする前に、文字列に正規化を適用する必要があります。つまり、大文字と小文字を区別しないDBを使用している場合は、小文字のみをハッシュします。 DBが先頭/末尾のスペースを処理する方法に応じて、スペースを最初または最後からトリミングすることもできます。
複合インデックスを使用して、GROUPBY句の列にインデックスを付けることが最初に試みられます。このようなクエリは、インデックスデータのみを使用して回答できる可能性があり、テーブルをスキャンする必要がまったくありません。インデックス内のレコードはソートされているため、DBMSはグループ処理の一部として個別のソートを実行する必要はありません。ただし、インデックスによってテーブルの更新が遅くなるため、テーブルで大量の更新が発生する場合は注意が必要です。
テーブルストレージにInnoDBを使用する場合、テーブルの行は主キーインデックスによって物理的にクラスター化されます。それ(またはその先頭部分)がたまたまGROUP BYキーと一致する場合、関連するレコードが一緒に取得されるため、このようなクエリが高速化されます。繰り返しますが、これにより、別のソートを実行する必要がなくなります。
一般に、ビットマップインデックスは別の効果的な代替手段ですが、私の知る限り、MySQLは現在これらをサポートしていません。
マテリアライズドビューは別の可能なアプローチですが、これもMySQLでは直接サポートされていません。ただし、COUNT統計を完全に最新にする必要がない場合は、定期的にCREATE TABLE ... AS SELECT ...
ステートメントを実行して、結果を手動でキャッシュできます。これは透明ではないので少し醜いですが、あなたの場合は許容できるかもしれません。
トリガーを使用して、論理レベルのキャッシュテーブルを維持することもできます。このテーブルには、GROUP BY句の各列に列があり、その特定のグループ化キー値の行数を格納するためのCount列があります。ベーステーブルに行が追加または更新されるたびに、その特定のグループ化キーのサマリーテーブルにカウンター行を挿入またはインクリメント/デクリメントします。キャッシュされたサマリーは常に最新であり、各更新は段階的に行われ、リソースへの影響が少ないため、これは偽のマテリアライズドビューアプローチよりも優れている可能性があります。ただし、キャッシュテーブルでのロックの競合には注意する必要があると思います。
InnoDBを使用している場合、count(*)およびその他の集計関数はテーブルスキャンを実行します。ここにいくつかの解決策があります。
update table set count = count + 1
のみを実行します。長所:高速更新、整合性(複数のクライアントが同じレコードを変更できる場合でも、ロックを使用することをお勧めします)。短所:ビジネスロジックとストレージを少し組み合わせます。クエリに使用しているエンジンを尋ねられた人が何人かいるようです。次の理由から、MyISAMを使用することを強くお勧めします。
InnoDB-@ Sorin Mocanuは、インデックスに関係なく全表スキャンを実行することを適切に識別しました。
MyISAM-常に現在の行数を手元に置いておきます。
最後に、@ justinが述べたように、適切なカバーインデックスがあることを確認してください。
CREATE INDEX ix_temp ON relations (relation_title, object_title);
count(myprimaryindexcolumn)をテストし、パフォーマンスをcount(*)と比較します
データベースに保持する特別な理由がない限り、またはデータを分割してクエリを個別に実行できる場合を除いて、データをアーカイブすることをお勧めします。
より多くのRAM/CPU/IOが本当に必要になるポイントがあります。あなたはあなたのハードウェアのためにそれを打ったかもしれません。
テーブルの合計行の1〜2%を超えるクエリにインデックスを使用することは、通常は効果的ではないことに注意してください(カバーしている場合を除く)。大規模なクエリがインデックスシークとブックマークルックアップを実行している場合は、1日の合計クエリからのキャッシュされたプランが原因である可能性があります。 WITH(INDEX = 0)を追加して、テーブルスキャンを強制し、より高速かどうかを確認してください。
テーブル全体のサイズがわからない場合は、メタテーブルまたは情報スキーマ(私が知っているすべてのDBMSに存在しますが、MySQLについてはよくわかりません)をクエリする必要があります。クエリが選択的である場合は、そのクエリのインデックスがあることを確認する必要があります。
AFAIKこれ以上できることはありません。