高速化する方法“ select count(*)” “ group by”および“ where&#8221 ;?
-
06-07-2019 - |
質問
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 のインデックスがうまく機能しませんでした。
解決
難易度を高めるために、いくつかのことを試してみます:
(簡単)-正しいカバーリングインデックスがあることを確認します
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でオブジェクトのタイトルのサイズが長い場合、object_titleのみをハッシュし、(relation_title、object_title_hash)
にインデックスを作成できます。
このソリューションは、これらのフィールドの一方または両方がハッシュのサイズに対して非常に長い場合にのみ役立つことに注意してください。
また、小文字の文字列のハッシュは大文字のハッシュと同じではないので、大文字小文字の区別/照合にハッシュの興味深い影響があることに注意してください。そのため、文字列をハッシュする前に正規化を文字列に適用する必要があります。つまり、大文字と小文字を区別しないDBの場合は小文字のみをハッシュします。また、DBが先頭/末尾のスペースを処理する方法に応じて、先頭または末尾からスペースをトリミングすることもできます。
他のヒント
GROUP BY句の列にインデックスを付けることは、複合インデックスを使用して最初に試すことです。このようなクエリは、インデックスデータのみを使用して応答できるため、テーブルをスキャンする必要がまったくありません。インデックス内のレコードはソートされるため、DBMSはグループ処理の一部として別個のソートを実行する必要はありません。ただし、インデックスはテーブルの更新を遅くするため、テーブルの更新が多い場合は注意してください。
テーブルストレージにInnoDBを使用する場合、テーブルの行は主キーインデックスによって物理的にクラスター化されます。その(またはその先頭部分)がGROUP BYキーと一致した場合、関連レコードが一緒に取得されるため、このようなクエリが高速化されます。繰り返しますが、これにより、個別のソートを実行する必要がなくなります。
一般に、ビットマップインデックスは別の効果的な代替手段になりますが、MySQLは現在のところ、これらをサポートしていません。
マテリアライズドビューは別の可能なアプローチですが、これもMySQLで直接サポートされていません。ただし、COUNT統計を完全に最新にする必要がない場合は、 CREATE TABLE ... AS SELECT ...
ステートメントを定期的に実行して、結果を手動でキャッシュできます。これは透明ではないので少しいですが、あなたの場合は受け入れられるかもしれません。
トリガーを使用して論理レベルのキャッシュテーブルを維持することもできます。このテーブルには、GROUP BY句の各列に対応する列があり、特定のグループ化キー値の行数を格納するCount列があります。ベーステーブルに行が追加または更新されるたびに、その特定のグループ化キーのサマリーテーブルにカウンター行を挿入またはインクリメント/デクリメントします。キャッシュされたサマリーは常に最新であり、各更新は増分的に行われ、リソースへの影響が少ないため、これは偽のマテリアライズドビューアプローチよりも優れている場合があります。ただし、キャッシュテーブルのロック競合に注意する必要があると思います。
InnoDBがある場合、count(*)およびその他の集計関数はテーブルスキャンを実行します。ここにいくつかの解決策があります:
- トリガーを使用し、集計を別のテーブルに保存します。長所:整合性。短所:更新が遅い
- 処理キューを使用します。長所:高速アップデート。短所:キューが処理されるまで古い状態が続く可能性があるため、ユーザーは整合性の欠如を感じる可能性があります。
- ストレージアクセスレイヤーを完全に分離し、集計を別のテーブルに格納します。ストレージ層はデータ構造を認識し、フルカウントを行う代わりにデルタを適用できます。たとえば、" addObject"を指定した場合、その中の機能は、オブジェクトがいつ追加され、集約が影響を受けるかを知っています。次に、
update table set count = count + 1
のみを実行します。長所:高速更新、整合性(複数のクライアントが同じレコードを変更できる場合はロックを使用することもできます)。短所:ビジネスロジックとストレージを少し組み合わせます。
私は、あなたがクエリにどのエンジンを使用しているのかを尋ねた人がいるようです。次の理由でMyISAMを使用することを強くお勧めします。
InnoDB -@Sorin Mocanuは、インデックスに関係なく全表スキャンを実行することを適切に識別しました。
MyISAM -現在の行数を常に手元に保持します。
最後に、@ justinが述べたように、適切なカバーリングインデックスがあることを確認してください:
CREATE INDEX ix_temp ON relations (relation_title, object_title);
テスト count(myprimaryindexcolumn) パフォーマンスをカウントと比較します(*)
本当に必要なポイントがあります より多くのRAM / CPU / IO。お使いのハードウェアで問題が発生している可能性があります。
通常、インデックスを使用するのは効果的ではありません(ただし、 カバー)テーブル内の合計行の1〜2%以上をヒットするクエリの場合。 大規模なクエリがインデックスシークとブックマークルックアップを実行している場合、 キャッシュされたプランは、1日の合計クエリからのものであるためです。追加してみてください WITH(INDEX = 0)を使用して、テーブルスキャンを強制し、より高速かどうかを確認します。
テーブル全体のサイズがわかったら、メタテーブルまたは情報スキーマ(私が知っているすべてのDBMSに存在しますが、MySQLについてはわかりません)を照会する必要があります。クエリが選択的である場合、インデックスが存在することを確認する必要があります。
知りたいことはもうありません。
データベースに保存する特別な理由がない限り、データをアーカイブするか、データを分割してクエリを個別に実行することをお勧めします。