始めに、私がこれを尋ねている理由は、自分の見積もりによると、メモリに収まらないインデックスのために、大量の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
_
どんなインデックス?インデックスがありません!したがって、どのクエリでもテーブル全体、つまりすべてのパーティションがスキャンされます。テーブル全体が_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 を参照してください。
(私の以前の答えはまだ当てはまりますが、INDEXes
とSELECTs
が利用可能になる前に書かれました。)
最適なインデックス
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のいずれかが役立ちます。オプティマイザーmight各AND
句に必要な行数の見積もりに基づいて最適なものを選択します。
_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
からclients
とchannels
に至るまでに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次元の問題があります(a
とb
の範囲、およびg
(LIKE
の場合))。 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
_?