みなさん、こんにちは。
データテーブル内のレコードをカウントするとき、カウント関数を使用してカウントすることに慣れていますが、カウント関数に渡すことができるパラメータには、 count(1) 、 count() 、 count(column) など、さまざまな種類があります。 …
それで、どれが最も効果的ですか?count(*) は最も効率が悪いのではないでしょうか?
count(*) は最も効率が低いと考えられ、クエリのようにテーブル内のすべてのフィールドを読み取るものと想定されることがよくありますSELECT * FROM
。これは本当ですか? 以下で調べてみましょう。
1. どの count ステートメントが最高のパフォーマンスを発揮しますか?
前回の結論から始めます。
coun(*) = count(1) > count(primary key column) > count(column)
これを理解するには、count() 関数の動作原理をさらに深く掘り下げる必要があります。次に進む前に、記事の内容について同意しましょう。
- データベース: MySQL
- ストアエンジン: InnoDB
1.1. count()とは何ですか?
Count(arg) は集計関数であり、count() 関数のパラメータは列、定数、または偶数 (*) です。この関数は、クエリ条件を満たすレコードの数をカウントするために使用され、count(arg) 関数のパラメータには異なる値が指定されていますnull
。count 関数の引数が次のようなフィールドであるとします。
select count(name) from member;
このステートメントは、 以外の名前フィールドを持つメンバー テーブル内のレコードの数をカウントしますnull
。つまり、レコードの名前フィールドの値が の場合null
、カウントされません。次のように、count() 関数のパラメーターが定数 1 であると仮定します。
select count(1) from member;
もちろん 1 は常に異なるnull
ため、上記のステートメントはメンバー テーブルにレコードがいくつあるかをカウントします。より深く理解するために、count 関数の操作メカニズムと実行戦略について学びましょう。
1.2. Count(主キー)はどのように機能しますか?
count(arg) 関数を使用してレコードの数をカウントすると、MySQL は count という変数を保持し、1 回のパスでレコードを読み取ります。count 関数の arg 値が と異なる場合null
、すべてのレコードが参照されるまで count 変数に 1 を追加し、その後ループを終了します。最後に、count 変数の値をクライアントに返します。
ご存知のとおり、インデックスにはクラスター化インデックスとセカンダリ インデックスの 2 種類があります。それらの違いは、クラスター化インデックスのリーフ ノードは実際のデータを保存するのに対し、セカンダリ インデックスのリーフ ノードはレコードのデータではなく主キー値のみを保存することです。デフォルトでは、主キーを作成するとクラスター化インデックスが自動的に作成され、InnoDB は B+ ツリーのリーフ ノードにレコードを保存します。
例として次のコマンドを取り上げます。
select count(CardNo) from member;
テーブルにクラスター化インデックスのみがあり、2 番目のインデックスがない場合、InnoDB はクラスター化インデックスを参照します。InnoDB はレコードごとに主キー値を読み取り、比較しますnull
。異なる場合はnull
count 変数に 1 を加算します。
別の例Phone_No
としては、主キーがあり、(Phone_No, Name)
テーブル内に2 番目のインデックスが存在しますMember
。
ただし、テーブルにセカンダリ インデックスがある場合、InnoDB はクラスター化インデックスではなくセカンダリ インデックスを参照します。その理由は、セカンダリ インデックスが占めるストレージ領域がクラスター化インデックスよりも少ないためです => セカンダリ インデックス ツリーがクラスター化インデックス ツリーよりも小さい => 2 番目のインデックスをスキャンする場合の I/O コストがクラスター化インデックスをスキャンするよりも低いためです。したがって、オプティマイザはセカンダリ インデックスの使用を優先します。
1.3. Count(1) はどのように機能しますか?
count 関数のパラメーターは 1 ですが、これは明らかに列ではなく、列でもありませんnull
。それで、count(1)
それはどのように機能するのでしょうか?
例えば:
select count(1) from member;
クラスター化インデックスのみでセカンダリ インデックスがない場合、InnoDB はクラスター化インデックスを参照してレコードをカウントしますが、主キーの値を読み取ることなく、レコードのみを参照します。レコードの有無が異なるとみなされるためnull
。
比較するためにレコード値を読み取る必要がないため、 Count(1)
処理が高速になることがわかります。ただし、テーブルにセカンダリ インデックスがある場合、InnoDB は最初にセカンダリ インデックスを参照します。 Count(primary key column)
null
1.4. Count(*) はどのように機能しますか?
* 文字を見ると、レコード内のすべてのフィールド値が読み取られていると思いますか?
の場合は true になりますselect *
が、そうでcount(*)
ない場合は成立しません。 を呼び出すとcount(*)
、MySQL はパラメータを に変換しますcount(0)
。
したがって、count(*) の実装は count(1) と同一であり、パフォーマンスの違いはありません。
1.5. count(列)はどのように機能しますか?
次のような例があります。
select count(name) from member;
このクエリでは、MySQL はテーブル全体をスキャンしてカウントするため、Count(column) は count(1)、count(*)、および count(primary key columns) と比較して最もパフォーマンスが悪くなります。
列にセカンダリ インデックスがある場合、コマンドはそのインデックスを使用して参照するため、速度が向上します。
1.6. まとめ
coun(*) = count(1) > count(primary key column) > count(column)
coun(*)
または を使用することをお勧めしますcount(1)
。
テーブルにセカンダリ インデックスがある場合、InnoDB は参照するセカンダリ インデックスを選択します。プライマリ インデックスを参照するよりも効率的であるためです。
上記に 2 番目のインデックスがない場合は、テーブル全体をスキャンする可能性があるため、レコード数のカウントにはcolumn X
使用しないでください=> 最も効果的ではありません。Count(column X)
2. その他の場合のカウント
2.1. Storage Engine は count(*) とどう違うのですか?
なぜ count() 関数がレコードを反復処理する必要があるのか疑問に思われるかもしれません。
記事の冒頭では InnoDB ストレージ エンジンについてのみ説明しましたが、ストレージ エンジンが異なれば count 関数の実装方法も異なる場合があります。たとえば、別の MySQL ストレージ エンジンである MyISAM は、InnoDB に次いで 2 番目に人気があります。
MyISAM を使用する場合、各テーブルには値 を含むメタデータが含まれますrow_count
。したがって、テーブル内のすべてのレコードをカウントするrow_count
必要がある場合(フィルター条件なしで count())、MyISAM はO(1) 複雑度 で値を読み取るだけで済みます。
count() にフィルター条件がある場合、MyISAM と InnoDB の動作は変わりません。どちらもテーブルをスキャンして、一致するレコードの数を数える必要があります。
* を読み取るときrow_count
、MyISAM はこの値の一貫性を確保するためにテーブルをロックすることに注意してください。
2.2. トランザクション内のカウントはどのように機能しますか?
ストレージ エンジン InnoDB はトランザクションをサポートしており、複数のトランザクションを同時に実行できます。MVCC (マルチバージョン同時実行制御) および分離メカニズムは、count() の結果に影響を与える可能性があります。
たとえば、メンバー テーブルには 100 個のレコードがあります。また、2 つのセッションが並行して実行され、クエリは次の順序で実行されます。
セッション A とセッション B の終了時に、メンバー テーブルのレコードの総数を同時に確認しますが、表示される結果が異なることがわかります。トランザクション A のデフォルトの分離レベルは反復可能であるため、count(*)
2 番目のトランザクションでは結果が 100 として反復されます。
InnoDB は、トランザクションの分離を確保するために、UNDO ログ内のデータを参照する必要があります。MVCC と絶縁計算の詳細については、こちらをご覧ください。
3. count(*) を最適化するにはどうすればよいですか?
大きなテーブルに対して count(*) を頻繁に使用する場合、それは良い解決策ではありません。
たとえば、メンバー テーブルには合計 1,200 万件以上のレコードがあり、セカンダリ インデックスも作成しましたが、1 回の実行に約 5 秒かかります。select count(*) from member
では、大きなテーブルに面しているときにこれを行うためのより良い方法はあるでしょうか?
3.1. 近似値を取得する
統計的な数値をそれほど正確にする必要がない場合 (たとえば、検索エンジンがキーワードを検索する場合)、表示される検索結果の数は近似値です。
この場合、explain コマンドを使用してテーブルを推定できます。EXPLAIN コマンド (ANALYZE パラメータなし) は、実際にクエリを実行しないため、非常に効果的です。
3.2. count変数の値を保存するテーブルを作成する
テーブル内のレコードの正確な合計数を取得したい場合は、このカウント値を別のカウント テーブルに保存できます。データテーブルにレコードを追加するときは count フィールドを 1 つ増やし、レコードを削除するときは count フィールドを 1 つ減らします。
まとめ
最後に、いくつかの重要な点を覚えておく必要があります。
- Count(*) は Count(pk)、count(column) よりもパフォーマンスが優れています。
- count 関数は、カウントの実行にセカンダリ インデックスを使用することを好みます。
- 正確な数値が必要ない場合は、近似値を使用してください。
また次の記事でお会いしましょう。
皆さんも面白いと思ったら、ぜひ高評価 🔼 とシェアをお願いします。
みなさん、本当にありがとうございました🙏
出典 : https://viblo.asia/p/su-khac-biet-giua-count-va-count1-cai-nao-hieu-qua-hon-vlZL9a2BLQK