web-dev-qa-db-ja.com

MySQLは非常に大きなテーブルでのパフォーマンスをカウントします

Innodbには1億行を超えるテーブルがあります。

外部キーが1である行が5000行以上あるかどうかを知る必要があります。正確な数は必要ありません。

私はいくつかのテストを行いました:

SELECT COUNT(*) FROM table WHERE fk = 1 => 16秒
SELECT COUNT(*) FROM table WHERE fk = 1 LIMIT 5000 => 16秒
SELECT primary FROM table WHERE fk = 1 => 0.6秒

より大きなネットワークと治療時間を持つことになりますが、15.4秒の過負荷になる可能性があります。

もっと良いアイデアはありますか?

ありがとう

編集:[OPの関連コメントを追加]

SELECT SQL_NO_CACHE COUNT(fk)FROM table WHERE fk = 1を試しましたが、25秒かかりました

Mysqlは、Mysql Tunerを使用してInnodb用に調整されました。

CREATE TABLE table ( pk bigint(20) NOT NULL AUTO_INCREMENT,
fk tinyint(3) unsigned DEFAULT '0', 
PRIMARY KEY (pk), KEY idx_fk (fk) USING BTREE ) 
ENGINE=InnoDB AUTO_INCREMENT=100380914 DEFAULT CHARSET=latin1

DBスタッフ:

'have_innodb', 'YES' 'ignore_builtin_innodb', 'OFF' 'innodb_adaptive_hash_index', 'ON'    
'innodb_additional_mem_pool_size', '20971520' 'innodb_autoextend_increment', '8' 
'innodb_autoinc_lock_mode', '1' 'innodb_buffer_pool_size', '25769803776' 
'innodb_checksums', 'ON' 'innodb_commit_concurrency', '0',
'innodb_concurrency_tickets', '500' 'innodb_data_file_path',
'ibdata1:10M:autoextend' 'innodb_data_home_dir', '', 'innodb_doublewrite', 'ON'     
'innodb_fast_shutdown', '1' 'innodb_file_io_threads', '4' 
'innodb_file_per_table', 'OFF', 'innodb_flush_log_at_trx_commit', '1' 
'innodb_flush_method', '' 'innodb_force_recovery', '0' 'innodb_lock_wait_timeout', '50' 
'innodb_locks_unsafe_for_binlog', 'OFF' 'innodb_log_buffer_size', '8388608' 
'innodb_log_file_size', '26214400' 'innodb_log_files_in_group', '2' 
'innodb_log_group_home_dir', './' 'innodb_max_dirty_pages_pct', '90'     
'innodb_max_purge_lag', '0' 'innodb_mirrored_log_groups', '1' 'innodb_open_files', 
'300' 'innodb_rollback_on_timeout', 'OFF' 'innodb_stats_on_metadata', 'ON' 
'innodb_support_xa', 'ON' 'innodb_sync_spin_loops', '20' 'innodb_table_locks', 'ON' 
'innodb_thread_concurrency', '8' 'innodb_thread_sleep_delay', '10000'      
'innodb_use_legacy_cardinality_algorithm', 'ON'

pdate '15:これまで同じ方法を使用し、1日あたり6億行と640 000の新しい行を使用しました。まだ正常に動作しています。

35
hotips

最後に、C#を使用して最初のX行を照会し、行数をカウントするのが最速でした。

私のアプリケーションはデータをバッチで処理しています。 2つのバッチ間の時間は、処理する必要がある行の数に依存します

SELECT pk FROM table WHERE fk = 1 LIMIT X

0.9秒で結果が得られました。

あなたのアイデアに感謝します!

1
hotips

実際のカウントには興味がないようですので、これを試してみてください。

SELECT 1 FROM table WHERE fk = 1 LIMIT 5000, 1

行が返される場合、5000以上のレコードがあります。 fk列にはインデックスが付けられていると思います。

19
Salman A

カウンターテーブルまたはその他のキャッシュメカニズムが解決策です。

InnoDBはテーブル内の行の内部カウントを保持しません。これは、同時トランザクションが同時に異なる数の行を「見る」可能性があるためです。 SELECT COUNT(*)FROM tステートメントを処理するために、InnoDBはテーブルのインデックスをスキャンします。インデックスが完全にバッファプールにない場合は時間がかかります。テーブルが頻繁に変更されない場合は、MySQLクエリキャッシュを使用することをお勧めします。高速カウントを取得するには、自分で作成したカウンターテーブルを使用し、挿入と削除に応じてアプリケーションに更新させる必要があります。おおよその行数で十分な場合は、SHOW TABLE STATUSを使用できます。 セクション14.3.14.1「InnoDBパフォーマンスチューニングのヒント」 を参照してください。

19
scriptin

別の回答を追加する必要があります-これまでに、コメントと回答に多くの修正/追加があります。

MyISAMの場合、WHEREなしのSELECT COUNT(*)は推測されます-非常に高速です。他のすべての状況(質問にInnoDBを含む)は、回答を得るためにデータのBTreeまたはインデックスのBTreeをカウントする必要があります。そのため、どれだけ数えるかを確認する必要があります。

InnoDBはデータとインデックスブロックをキャッシュします(各16 KB)。しかし、テーブルのデータまたはインデックスBTreeが_innodb_buffer_pool_size_より大きい場合、ディスクにヒットすることが保証されます。ほとんどの場合、ディスクのヒットはSQLの最も遅い部分です。

クエリキャッシュを使用すると、通常、クエリ時間は約1ミリ秒になります。これは、引用されたタイミングのいずれにおいても問題ではないようです。だから、私はそれにこだわらない。

しかし...sameクエリを2回で実行すると、示す:

  • 最初の実行:10秒
  • 2回目の実行:1秒

これは、最初の実行でほとんどのブロックをディスクからフェッチする必要があることを示していますが、2番目の実行では、すべてをRAM(buffer_pool)気づかないthisキャッシングの問題(16秒vs 0.6秒mayこれで説明されます。)

SQLの方が速いrealメトリックとして、「ディスクヒット」または「タッチする必要があるブロック」をハープします。

COUNT(x)は、集計前にxの_IS NOT NULL_をチェックします。これにより、わずかな処理が追加されますが、ディスクヒットの数は変わりません。

提供されるテーブルには、PKと2番目の列があります。それはrealテーブルなのだろうか?それは違いを生む-

  • オプティマイザーがdataを読み取ることを決定した場合-つまり、_PRIMARY KEY_順序でスキャンします-データBTreeを読み取ります。 isusually(ただし、このラメの例ではない)セカンダリインデックスBTreeよりもはるかに広い。
  • オプティマイザーがセカンダリインデックスを読み取ることを決定した場合(ただし、並べ替えを行う必要はありません)、タッチするブロックは少なくなります。したがって、高速。

元のクエリに関するコメント:

_SELECT COUNT(*) FROM table WHERE fk = 1 => 16 seconds
    -- INDEX(fk) is optimal, but see below
SELECT COUNT(*) FROM table WHERE fk = 1 LIMIT 5000 => 16 seconds
    -- the LIMIT does nothing, since there is only one row in the result
SELECT primary FROM table WHERE fk = 1 => 0.6 seconds
    -- Again INDEX(fk), but see below
_

_WHERE fk = 1_はINDEX(fk, ...)を要求し、できればINDEX(fk)のみを要求します。 InnoDBでは、各セカンダリインデックスにpkのコピーが含まれていることに注意してください。つまり、INDEX(fk)は実質的にINDEX(fk, primary)です。したがって、3番目のクエリはそれを「カバー」として使用でき、データに触れる必要はありません。

テーブルが本当に2つの列だけである場合、おそらくセカンダリインデックスBTreeはデータBTreeより太くなります。しかし、現実的なテーブルでは、セカンダリインデックスは小さくなります。したがって、インデックススキャンは、テーブルスキャンよりも高速(タッチするブロックが少ない)になります。

3番目のクエリも大きな結果セットを提供しています。これにより、クエリに時間がかかる場合があります-but引用符で囲まれた「時間」には含まれません。クエリ時間ではなく、ネットワーク時間です。

_innodb_buffer_pool_size = 25,769,803,776_テーブルとそのセカンダリインデックス(FKから)がそれぞれ約3〜4GBであると推測します。そのため、どのタイミングでもfirstで大量のものをロードする必要があります。次に、second実行が完全にキャッシュされます。 (もちろん、_fk=1_を持つ行の数はわかりません。おそらくすべての行より少ないのでしょうか?)

But... 6億行では、テーブルとそのインデックスはeach25GBのbuffer_poolに近づいています。したがって、I/Oバウンドになる日がすぐに来るかもしれません。これにより、16(または25)秒に戻りたいと思うようになります。まだできません。次に、COUNTを実行する代替方法について説明します。

_SELECT 1 FROM tbl WHERE fk = 1 LIMIT 5000,1_-これを分析しましょう。インデックスをスキャンしますが、5000行後に停止します。必要なのは「5K以上」です。これが最も良い方法です。テーブル内の行の総数に関係なく、一貫して高速になります(数十ブロックだけに触れます)。 (システムのbuffer_pool_sizeとキャッシュ特性の影響を受けます。ただし、コールドキャッシュであっても、1ダースのブロックにかかる時間は1秒未満です。)

MariaDBの _LIMIT ROWS_EXAMINED_ は検討する価値があります。それがなければ、あなたはできる

_SELECT COUNT(*) AS count_if_less_than_5K
    FROM ( SELECT 1 FROM tbl WHERE fk = 1 LIMIT 5000 );
_

それは、クライアントに行を配信するよりも高速ですmay。 tmpテーブルで行を内部的に収集する必要がありますが、COUNTのみを配信します。

サイドノート:1日あたり640K行が挿入されます-これは、SDDではなくHDDの現在の設定で、MySQLの単一行INSERTsの制限に近づきます。潜在的な災害について話し合う必要がある場合は、別の質問を開いてください。

結論:

  • クエリキャッシュは避けてください。 (_SQL_NO_CACHE_を使用するか、QCをオフにします)
  • タイミングクエリを2回実行します。 2回目を使用します。
  • 関連するBTreeの構造とサイズを理解します。
  • Nullチェックが必要でない限り、COUNT(x)を使用しないでください。
  • PHPの_mysql_*_インターフェイスを使用しないでください。 _mysqli_*_またはPDOに切り替えます。
6
Rick James

PHPを使用している場合は、mysql_num_rowsから得た結果SELECT primary FROM table WHERE fk = 1 => 0.6 seconds、私はそれが効率的だと思います。

ただし、使用しているサーバー側言語によって異なります

1
nischayn22

行数を知ることに興味がなく、値に対してCOUNTをテストするだけの場合は、次の標準スクリプトを使用できます。

SELECT 'X'
FROM mytable
WHERE myfield='A'
HAVING COUNT(*) >5

これは、条件が満たされるかどうかに応じて、単一の行を返すか、行をまったく返しません。

このスクリプトはANSIに準拠しており、COUNT(*)の完全な値を評価せずに完全に実行できます。 MySQLが何らかの条件が満たされた後に行の評価を停止するための最適化を実装した場合(そうすることを本当に望みます)、パフォーマンスが向上します。残念ながら、使用可能な大きなMySQLデータベースがないため、この動作を自分でテストすることはできません。このテストを行う場合は、ここで結果を共有してください:)

0
Gerardo Lima