web-dev-qa-db-ja.com

MySQLはインデックスに関してどのようにメモリを管理しますか?

始めに、私がこれを尋ねている理由は、自分の見積もりによると、メモリに収まらないインデックスのために、大量のI/Oでディスクを強制終了しているはずのデータベースがあると感じているからです。実際には、まだうまく機能しています。

関連するテーブルから始めましょう:

_CREATE TABLE `search` (
  `a` bigint(20) unsigned NOT NULL,
  `b` int(10) unsigned NOT NULL,
  `c` int(10) unsigned DEFAULT NULL,
  `d` int(10) unsigned DEFAULT NULL,
  `e` varchar(255) DEFAULT NULL,
  `f` varchar(255) DEFAULT NULL,
  `g` varchar(255) DEFAULT NULL,
  `h` varchar(255) DEFAULT NULL,
  `i` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
_

a列は、タイムスタンプ(秒単位)がエンコードされた8バイトの数字です。テーブルにはPARTITION BY RANGE (a)があり、テーブルを月次パーティションに分割します。これは、データベースに24か月しか保持せず、残りは削除されるためです。

テーブルは1か月あたり約2億行ずつ増加します。テーブル全体には約50億行が含まれます。

それが実行されるサーバーには約360GBのメモリがあり、そのうちの300GBはMySQL用に予約されています。私が興味深いと思うのは、少し前にディスク使用率が少し上昇し始めたことです。現在、これは特定のインデックスがメモリに収まらなくなり、MySQLがディスクからそれらをロードするためであると考えていますが、これは単なる推測です。 MySQLの内部に慣れていません。

特定の時間に、または特定のクエリでメモリにロードされているページ/ブロックを確認する方法はありますか?


これらは実際に使用されている3つのテーブルです。

_CREATE TABLE `search` (
  `a` bigint(20) unsigned NOT NULL,
  `b` int(10) unsigned NOT NULL,
  `c` int(10) unsigned DEFAULT NULL,
  `d` int(10) unsigned DEFAULT NULL,
  `e` varchar(255) DEFAULT NULL,
  `f` varchar(255) DEFAULT NULL,
  `g` varchar(255) DEFAULT NULL,
  `h` varchar(255) DEFAULT NULL,
  `i` varchar(255) DEFAULT NULL,
  KEY `a_idx` (`a`),
  KEY `b_idx` (`b`),
  KEY `c_idx` (`c`, `a`),
  KEY `d_idx` (`d`, `a`),
  KEY `e_idx` (`e`, `a`),
  KEY `f_idx` (`f`, `a`),
  KEY `g_idx` (`g`, `a`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `channels` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE `clients` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `client_hash` varchar(4095) NOT NULL,
   PRIMARY KEY (`id`),
   KEY `hash_idx` (`client_hash`(255))
) ENGINE=InnoDB DEFAULT CHARSET=utf8
_

これらは現在実行中のクエリです:

_SELECT      S.a,
            S.b,
            S.e,
            S.f,
            S.g,
            S.h,
            S.i,
            C1.client_hash,
            C2.name
FROM        search S
LEFT JOIN   clients C1
ON          S.c = C1.id
LEFT JOIN   channels C2 
ON          S.d = C2.id
WHERE       S.e = "foo"
AND         S.a >= 6409642363135721472
AND         S.a <= 6443039964404908032
AND         S.b >= 1492361157
AND         S.b <= 1500137142
ORDER BY    S.a DESC
LIMIT       50

SELECT      S.a,
            S.b,
            S.e,
            S.f,
            S.g,
            S.h,
            S.i,
            C1.client_hash,
            C2.name
FROM        search S
LEFT JOIN   clients C1
ON          S.c = C1.id
LEFT JOIN   channels C2 
ON          S.d = C2.id
WHERE       S.f = "bar"
AND         S.a >= 6409642363135721472
AND         S.b >= 1492361157
ORDER BY    S.a DESC
LIMIT       50

SELECT      S.a,
            S.b,
            S.e,
            S.f,
            S.g,
            S.h,
            S.i,
            C1.client_hash,
            C2.name
FROM        search S
LEFT JOIN   clients C1
ON          S.c = C1.id
LEFT JOIN   channels C2 
ON          S.d = C2.id
WHERE       S.g = "baz"
AND         S.a >= 6409642363135721472
AND         S.b >= 1492361157
ORDER BY    S.a DESC
LIMIT       50

SELECT      S.a,
            S.b,
            S.e,
            S.f,
            S.g,
            S.h,
            S.i,
            C1.client_hash,
            C2.name
FROM        search S
LEFT JOIN   clients C1
ON          S.c = C1.id
LEFT JOIN   channels C2 
ON          S.d = C2.id
WHERE       S.g LIKE "baz%"
AND         S.a >= 6409642363135721472
AND         S.b >= 1492361157
ORDER BY    S.a DESC
LIMIT       50
_
4
Aeveus

どんなインデックス?インデックスがありません!したがって、どのクエリでもテーブル全体、つまりすべてのパーティションがスキャンされます。テーブル全体が_innodb_buffer_pool_size_より大きくなると、ディスクをヒットする必要がない限り、テーブルスキャンは完了しません。そして、次のテーブルスキャンでは、ディスクからすべてが再読み取りされます。

インデックスをメモリに保持する必要はありません。これはテーブルのように機能します。16KBのブロックで構成され、必要に応じてバッファプールにキャッシュされ、「古い」ときにバンプされます(「最近使用されていない」キャッシュスキームと考えてください)。

ここでも、フルindexスキャンを実行し、インデックスがバッファープールに収まらない場合、キャッシュは役に立たなくなり、常にディスク。

しかし...インデックスの適切な定義と使用は、その運命に終わる必要はありません。テラバイトサイズのテーブルが32 GBのRAMで正常に動作することを確認しました。特に、「ポイントクエリ」(_... WHERE primary_key = constant ..._)は、テーブルのサイズやbuffer_poolのサイズに関係なく、1秒未満で完了します。最悪の場合(コールドキャッシュ)、10億行のテーブルでは、要求した単一の行を見つけるためにBTreeで5ブロックをフェッチする必要がある場合があります。

PARTITION BY RANGE(id)はほとんどの場合役に立たない。代わりに、パーティション化なしのPRIMARY KEY(id)は、idによって行を見つけるbetterジョブを実行します。

Buffer_poolにあるものを見るためのツールがありますが、あなたが求めているものに対処するために2,000万のブロック番号を扱うのは嫌です!

代わりに、実際の_SHOW CREATE TABLE_(インデックス/パーティションを確認できるようにするため)およびをいくつか見てみましょうSELECTs。それらから、私たちは裏で何が起こっているかについて議論することができます。これは、はるかに速く、より有益です。

最適なインデックスの作成については、 my cookbook も参照してください。 PARTITIONingの限られたユーティリティについては、 my partition blog を参照してください。

3
Rick James

(私の以前の答えはまだ当てはまりますが、INDEXesSELECTsが利用可能になる前に書かれました。)

最適なインデックス

4つのクエリはすべてこのように見えますか?

_SELECT  S.a, S.b, S.e, S.f, S.g, S.h, S.i, C1.client_hash, C2.name
    FROM  search S
    LEFT JOIN  clients  C1  ON S.c = C1.id
    LEFT JOIN  channels C2  ON S.d = C2.id
    WHERE  S.<some-column> = "..."   -- or LIKE
      AND  S.a >= 6409642363135721472
      AND  S.b ... (some range)
    ORDER BY  S.a DESC
    LIMIT  50 
_

(少なくとも)e、f、gはどこですか?.

これらはSの唯一の有用なインデックスであると思います:

_INDEX(e, a)
INDEX(f, a)
INDEX(g, a)
_

E/f/gを定数と比較する場合、これらはすべてINDEX(g,a)によって処理されます。

_WHERE S.g = "baz"
  AND S.a >= constant
ORDER BY S.a
LIMIT 50
_

テスト_S.b >= constant_は50行を超えて拡張しますが、うまくいけばテーブル全体ではありませんか?少なくともfilesortは避けられます。

LIKEも機能しません

_S.g LIKE "baz%"_の場合、次の3つのインデックスmayのいずれかが役立ちます。オプティマイザーmightAND句に必要な行数の見積もりに基づいて最適なものを選択します。

_INDEX(g, a) -- already asked for this; it will use only the `g` part
INDEX(a) -- hoping to get `S.a >= constant ORDER BY S.a LIMIT`
INDEX(b) -- in case it filters well (but not if partitioned by b)
_

したがって、5つのインデックスをお勧めします。

50に削減

_LIMIT 50_のため、次のように変更します。理論的根拠は、_ORDER BY .. LIMIT_ mightを実行するためのランプが50行をはるかに超えて収集する必要があるということです。そうすることで、50 [JOINsからclientschannelsに至るまでに50を超えることになります。したがって、この再公式化により、これらのルックアップは50に制限されます。

_SELECT  S.a, S.b, S.e, S.f, S.g, S.h, S.i,
        ( SELECT client_hash FROM clients WHERE id = S.c ) AS client_hash,
        ( SELECT name       FROM channels WHERE id = S.d ) AS channel_name
    FROM  search S
    WHERE  S.<some-column> =/LIKE ...
      AND  S.a .. some range
      AND  S.b .. some range
    ORDER BY  S.a DESC
    LIMIT  50 
_

_LEFT JOINs_がサブクエリに変わったことに注意してください。結果は同じになるはずです。

[〜#〜]パーティション[〜#〜]

2次元または3次元の問題があります(abの範囲、およびgLIKEの場合))。 2DはPARTITIONingのまれな使用例の1つです。それがyourクエリに適用されるかどうかの質問です。

データセットに関する知識がほとんどないことに基づいた、私の推測は次のとおりです。

_PARTITION BY RANGE(b)
_

20-50のパーティションがあります。 bでの範囲テストによって、必要なデータが1つ(または非常に少ない)パーティションに制限され、それによって作業が少なくなることが期待されます。

PARTITION BY RANGE(id)について質問しましたが、まだidがテーブルにありません。一意の列(または列の組み合わせ)はありますか? _PRIMARY KEY_はありますか?これらに答えてください。 I may PKをデータのクラスタリングに利用する方法について役立つヒントがあります。

(パーティション分割をパーティション分割で行う場合は、インデックスの推奨事項を変更する場合があります。)

aまたはbは冗長であるため

aは保持しているがbは削除していると仮定すると、

_WHERE  S.<some-column> =/LIKE ...
  AND  S.a .. some range
  AND  S.b .. some range
ORDER BY  S.a DESC
_

なるはず

_WHERE  S.<some-column> =/LIKE ...
  AND  S.a .. some range
ORDER BY  S.a DESC
_

そしてINDEX(b)はなくなります。そのため、4つのインデックスが必要になります提供されたクエリの場合

これらの変更を行ってから、LIKEクエリが十分に機能するかどうか、および他のクエリをディスカッションに組み込む必要があるかどうかを再評価することをお勧めします。つまり、追加する価値があるかどうかを確認するまでは、PARTITIONingを使用しません。

パーティション化に関連するその他の質問:新しい行が継続的に追加されていますか?古いタイムスタンプはDELETEdですか?

どちらがより選択的ですか? _S.g LIKE "baz%"_?または_S.a >= 6409642363135721472_?

1
Rick James