41

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は、MysqlTunerを使用して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'

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

4

7 に答える 7

27

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

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

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

于 2012-06-11T08:44:06.513 に答える
22

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

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

于 2012-06-11T08:47:47.580 に答える
8

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

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

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

クエリ キャッシュが関与する場合、通常、クエリ時間は約 1 ミリ秒になります。これは、引用されているどのタイミングでも問題ではないようです。だから私はそれにこだわるつもりはありません。

しかし...同じクエリを2回続けて実行すると、次のことがよく発生します。

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

これは、最初の実行ではほとんどのブロックをディスクからフェッチする必要があり、2 回目の実行ではすべてが RAM (buffer_pool) にあることを示しています。このキャッシュの問題に気付いていないため、リストされているタイミングのいくつかは偽物であると思われます。(16秒対0.6秒はこれで説明できるかもしれません。)

どの SQL の方が高速であるかの実際の指標として、「ディスク ヒット数」または「処理が必要なブロック数」を強調します。

COUNT(x)集計前に確認xします。IS NOT NULLこれにより、少量の処理が追加されますが、ディスク ヒットの数は変わりません。

提示されたテーブルには、PK と 2 番目の列があります。それが本当のテーブルなのかな?? それは違いを生む -

  • オプティマイザーがデータを読み取る(つまり、順番にスキャンする)ことを決定した場合PRIMARY KEY、データ BTree を読み取ることになります。これは、通常(ただし、この不十分な例ではありません)、セカンダリ インデックス 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 番目のクエリも大きな結果セットを提供しています。これにより、クエリに時間がかかる可能性がありますが、引用された「時間」には含まれません。クエリ時間ではなく、ネットワーク時間です。

innodb_buffer_pool_size = 25,769,803,776 テーブルとそのセカンダリ インデックス (FK から) は、それぞれ約 3 ~ 4 GB であると思います。そのため、どのタイミングでも最初に多くのものをロードする必要がある場合があります。その後、2 回目の実行は完全にキャッシュされます。(もちろん、何行あるかはわかりませんfk=1。おそらくすべての行よりも少ないのでしょうか?)

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

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

MariaDBLIMIT ROWS_EXAMINEDは調べる価値があるかもしれません。それがなければ、あなたはできる

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

行をクライアントに配信するよりも高速な場合があります行を内部的に tmp テーブルに収集する必要がありますが、COUNT.

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

結論:

  • クエリ キャッシュは避けてください。(SQL_NO_CACHEQCを使用またはオフにすることにより)
  • タイミング クエリを 2 回実行します。二回目利用。
  • 関連する BTree の構造とサイズを理解します。
  • COUNT(x)null チェックが必要でない限り、使用しないでください。
  • mysql_*PHP のインターフェースは使用しないでください。mysqli_*またはに切り替えPDOます。
于 2017-05-30T22:36:43.950 に答える
1

PHP を使用している場合mysql_num_rowsは、 から取得した結果に対して行うことができますが、SELECT primary FROM table WHERE fk = 1 => 0.6 seconds効率的であると思います。

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

于 2012-06-11T08:08:03.127 に答える
0

最後に、最速は、C#を使用して最初のX行をクエリし、行数をカウントすることでした。

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

SELECT pk FROM table WHERE fk = 1 LIMIT X

0.9秒で結果が出ました。

あなたのアイデアをありがとう!

于 2012-06-11T22:00:51.693 に答える
0

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

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

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

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

于 2012-06-11T09:30:36.377 に答える