web-dev-qa-db-ja.com

+15億行のmysqlテーブルのクエリ速度を推定する

行数(少なくとも15億行)に関して非常に大きなテーブルをクエリする必要があるオーディオフィンガープリント問題に取り組んでいますが、サイズは比較的良好(23G)であり、合計で約50K〜100K行を取得します。複数のクエリを使用する(20〜50クエリ)。

テーブルには、3つの列、ハッシュ、および2つのint値があります。制約は一切ありません。ハッシュ列には多くの衝突/重複があります。 show create tableの出力は次のとおりです

CREATE TABLE `fingerprints` (
  `hash` binary(10) NOT NULL,
  `int1` mediumint(8) unsigned NOT NULL,
  `int2` mediumint(8) unsigned NOT NULL,
  KEY `hash` (`hash`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

クエリは単純です。ここに例を示します。

select int1 ,int2 from fingerprints where hash in (UNHEX("1ff99335cce004f2765d"),UNHEX("14c4b93ed575982ed2e4"),UNHEX("41044b0cf21dc8ac8f9b"),UNHEX("a791403ca116b4da53dd"),UNHEX("d9f91514b900c25fa095"),UNHEX("3349f906deae6cd32883"),UNHEX("221c0e3e2bc243fb0fe5").... more here);

私はさまざまなハードウェア仕様を試しました(AWSを1つのマシン/インスタンスのみで使用)。 my.cnf構成は異なりますが、大幅なパフォーマンスの向上はありません。

この操作の目標速度しきい値(合計クエリ時間)は5秒です。しかし、私が平均して得た最高のものは、単一のクエリのみで3秒です(20クエリある場合、合計操作時間は1分です)。

最後の注意:クエリのプロファイリング時に、SHOWプロファイルコマンドは、最も遅い部分が(データの送信)状態であったことを示します。結果セットが大きい場合、クエリは遅くなります(つまり、1万行の取得には約6秒かかりますが、1000行の取得には2秒かかります)。

質問:

  • インデックスを保持するための十分なRAM.
  • 特定のデータベース設定についての推奨事項はありますか? mysqlメモリエンジンを試してみますか?ここでのパーティション分割は、分散マシンでは必要ですか? innodbに切り替えるべきですか?

私のセットアップ:

  • myisampackで圧縮され、where(ハッシュ)列にインデックスが付けられたmyisamテーブルのみを読み取ります。
  • インデックステーブル(MYIファイル)は完全にRAMに読み込まれます
  • Iopsが制限されたSSDハードディスク(Amazon AWS)。 AWSグラフによると、時々700 IOPSに達しています。

編集

SHOW INDEXの出力:

+--------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table        | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+--------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| fingerprints |          1 | hash     |            1 | hash        | A         |        NULL |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
+--------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+

EXPLAIN QUERY出力(クエリの例)


+----+-------------+--------------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
| id | select_type | table        | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+--------------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | fingerprints | NULL       | range | hash          | hash | 10      | NULL | 4912 |   100.00 | Using index condition |
+----+-------------+--------------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
1
msuliman

次のクエリを実行することで、この遅い問題を解決できました。

alter table fingerprints order by hash;
  1. ハッシュ列に多くの繰り返しがあります(34mの一意のハッシュのみがあります)。私が状況を正しく理解している場合、順序付けにより、私のユースケースでは読み取りがずっと連続的になりました(ハッシュから*を選択*テーブルから*を選択)。

  2. SHOW INDEXの出力を確認すると、カーディナリティ値はNULLです。コマンドによる順序を実行した後、カーディナリティは今=一意のハッシュの数= 34mです。それは理にかなっています。これが根本的な問題だと思います。参照してください: https://stackoverflow.com/questions/6521673/is-null-cardinality-in-an-index-a-problem-mysql-5-x

約60秒かかったジョブは、今では350ミリ秒しかかかりません。

1
msuliman

UNHEXingは問題の重要な部分ではありません。)

本当の問題は、ハッシュのランダム性です。ディスク上の多くの場所をジャンプすることにつながります。クエリを分析してみましょう。

  • INリストは、INDEX(hash)全体に散在する値のリストです。
  • MyISAMのkey_bufferにキャッシュされているB_Tree(_.MYI_ファイルにあります)をドリルダウンすることにより、各値が検索されます。 _key_buffer_size_の値は何ですか? `SHOW TABLE STATUS LIKE 'fingerprints'\Gの結果は何ですか?
  • Index_sizeがkey_buffer_sizeより大きい場合、ルックアップの多くがディスクにヒットします。
  • 各BTreeルックアップのリーフノードでは、5バイト(おそらく)の「レコード番号」になります。
  • ここで行を検索します-これは、オフセット= 17 * record_numberで_fingerprints.MYD_へのランダムディスクアクセス(シーク、BTreeなし)になります。 (レコードはFIXED長さ17バイトのようです。)
  • 繰り返しになりますが、ディスクヒットの可能性を調べています。ディスクの残りの空き領域がData_lengthよりも小さいと想定しています(表のステータスを参照)。

何をすべきか?

ケース1:Data_length + Index_length <RAM size:key_buffer_sizeをIndex_lengthより少し大きくします。徐々に両方のキャッシュがインデックスとデータで満たされ、I/Oはなくなります。

ケース2:その合計がRAMよりもわずかに大きい:キャッシュの1つを選択して、十分に大きくします。

ケース3:合計がRAMよりもはるかに大きい:より多くのRAMを取得するまで、大量のI/Oが発生します。

Data_lengthとIndex_lengthはほぼ等しいと思います。 availableRAM半分に分割します-key_buffer_sizeには半分、残りはデータキャッシングに使用します。

さらに2つのアイデアがあります。

  • 2番目のステップでintをフェッチするのではなく、KEY(hash, int1, int2)を使用します。これは、BTreeルックアップのみが必要であることを意味します。データはリーフノードに配置されます。このアプローチでは、_key_buffer_size_をavailableRAMの 'most'に設定できます。そのSELECTはデータを操作せず、インデックスのみを操作します。

  • InnoDBに切り替えます。ブロックは1KBではなく16KBです。これはかもしれない物事をより速くします。ただし、ディスクフットプリントは2〜3倍になります。ここでも、3列のインデックスを使用しますが、_key_buffer_size_を20Mに縮小し、_innodb_buffer_pool_size_をRAMの70%に増やします。

その他の注意事項:

  • 「データの送信」では何もわかりません。一般的に、プロファイリングは役に立ちません。

  • SSDはHDDよりもはるかに高速に動作します。

  • I/Oバインドされているようです。

  • I/Oバウンドかどうかに関係なく、合計クエリ時間は、検索されるハッシュの数にほぼ比例します。 (これは私の解剖から推測できます。)

  • MEMORYはMyISAMよりも大幅に高速または低速になる可能性は低いです。また、データを永続化する必要がある場合、MEMORYは揮発性であるため、面倒です。

  • 圧縮するのに6バイトしかないので、圧縮は役に立たないと予測します。 (そして、ハッシュ自体はおそらく圧縮可能ではありません。)

  • プロバイダーがIOPを制限している場合、それは問題です。インデックスが完全にキャッシュされている場合(そして、RAMを不必要に消費するほど大きくない場合)、IOPはデータブロックのフェッチです。 3バイトのキーは約70%大きくなります。十分な大きさのkey_bufferがRAMに収まりますか?もしそうなら、そのアプローチは最適かもしれません。

1
Rick James