Documentation Home
MySQL 5.6 リファレンスマニュアル
Download this Manual
PDF (US Ltr) - 27.1Mb
PDF (A4) - 27.1Mb
EPUB - 7.5Mb
HTML Download (TGZ) - 7.2Mb
HTML Download (Zip) - 7.2Mb


15.3 MEMORY ストレージエンジン

MEMORY ストレージエンジン (従来は HEAP と呼ばれていました) は、メモリーに格納された内容で特定用途のテーブルを作成します。データは、クラッシュ、ハードウェア問題、または電源停止に弱いため、これらのテーブルは、一時的な作業領域またはほかのテーブルから抽出されたデータの読み取り専用キャッシュとして使用されるだけです。

表 15.4 MEMORY ストレージエンジンの機能

ストレージの制限 RAM トランザクション いいえ ロック粒度 テーブル
MVCC いいえ 地理空間データ型のサポート いいえ 地理空間インデックスのサポート いいえ
B ツリーインデックス はい T ツリーインデックス いいえ ハッシュインデックス はい
全文検索インデックス いいえ クラスタ化されたインデックス いいえ データキャッシュ N/A
インデックスキャッシュ N/A 圧縮データ いいえ 暗号化データ[a] はい
クラスタデータベースのサポート いいえ レプリケーションのサポート[b] はい 外部キーのサポート いいえ
バックアップ/ポイントインタイムリカバリ[c] はい クエリーキャッシュのサポート はい データディクショナリ向け更新統計 はい

[a] ストレージエンジン内ではなくサーバー内で (暗号化関数を使って) 実装されています。

[b] ストレージエンジン内ではなくサーバー内で実装されています。

[c] ストレージエンジン内ではなくサーバー内で実装されています。


MEMORY または MySQL Cluster を使用する場合  重要で更新頻度の高い高可用性のデータに対して MEMORY ストレージエンジンを使用するアプリケーションを配備しようとする開発者は、MySQL Cluster がより良い選択かどうかを検討するはずです。MEMORY エンジンの典型的なユースケースには、次の特徴があります。

  • セッション管理やキャッシングなどの一時的で重要でないデータに関連する操作。MySQL サーバーが停止または再起動したときに、MEMORY テーブルのデータは失われます。

  • 高速アクセスおよび低待機時間のためのインメモリー保存。データボリュームはメモリー内に完全に収まり、オペレーティングシステムによる仮想メモリーページのスワップアウトはありません。

  • 読み取り専用または読み取りが大半のデータのアクセスパターン (更新が制限されています)。

MySQL Cluster は、MEMORY エンジンと同じ機能をより高いパフォーマンスレベルで提供し、MEMORY で利用できない追加機能を提供します。

  • クライアント間で競合の少ない行レベルロックとマルチスレッド操作。

  • 書き込みを含むステートメント混在時の拡張性。

  • データ持続性のためのディスクバックアップ式操作 (オプション)。

  • 単一障害点がない、シェアードナッシングアーキテクチャーと複数ホスト操作。99.999% の可用性を実現できます。

  • ノードをまたがる自動データ分散。アプリケーションの開発者はカスタムの共有またはパーティション化ソリューションを作る必要がありません。

  • 可変長データ型 (MEMORY がサポートしない BLOB および TEXT を含みます) をサポートします。

MEMORY ストレージエンジンと MySQL Cluster の詳細な比較に関するホワイトペーパーについては、MySQL Cluster による Web サービスの拡張: MySQL Memory ストレージエンジンの代替を参照してください。このホワイトペーパーには、2 つの技術のパフォーマンス調査と、既存の MEMORY ユーザーが MySQL Cluster にどのように移行できるかについて説明するステップバイステップガイドが含まれています。

パフォーマンスの特徴

MEMORY のパフォーマンスは、更新処理時のシングルスレッド実行とテーブルロックオーバーヘッドが原因の競合によって抑制されます。このため、負荷が増えたときに拡張性が制限されます (特に、書き込みを含むステートメント混在時)。

MEMORY テーブルのインメモリー処理にかかわらず、それらは、汎用目的クエリーのために、または読み取り/書き込み負荷では、必ずしもビジーサーバーの InnoDB テーブルより高速である必要はありません。特に、更新実行に関与するテーブルロックは、複数セッションからの MEMORY テーブルの並列使用の速度を低下させる可能性があります。

MEMORY テーブルで実行されるクエリーの種類によっては、デフォルトのハッシュデータ構造 (一意キーで 1 つの値を検索する場合)、または汎用目的の B ツリーデータ構造 (等号、不等号、未満または「- を超える」などの範囲演算子などを含むすべての種類のクエリーの場合) のいずれかとしてインデックスを作成する場合があります。次のセクションでは、両方の種類のインデックスを作成するための構文について説明します。パフォーマンス面でよくある問題は、B ツリーインデックスがより効率的な作業負荷で、デフォルトのハッシュインデックスを使用していることです。

MEMORY テーブルの物理特性

MEMORY ストレージエンジンは、各テーブルと 1 つのディスクファイル (テーブルの定義を格納 (データではありません)) を関連付けます。ファイル名はテーブル名から始まり、.frm 拡張子が付きます。

MEMORY テーブルには次のような特徴があります。

  • MEMORY テーブルの領域は小さなブロックに割り当てられます。テーブルは、挿入に 100% 動的ハッシュを使用します。オーバーフロー領域や余分なキー領域は必要ありません。フリーリスト用の余分な領域は必要ありません。削除された行はリンクリストに置かれ、新しいデータをテーブルに挿入するときに再利用されます。MEMORY テーブルでは、ハッシュテーブルで一般的に削除 + 挿入に関連付けられる問題も起こりません。

  • MEMORY テーブルは固定長の行ストレージフォーマットを使用します。VARCHAR などの可変長型は、固定長を使用して格納されます。

  • MEMORY テーブルは BLOB または TEXT カラムを含むことができません。

  • MEMORYAUTO_INCREMENT カラムのサポートを含みます。

  • TEMPORARY MEMORY でないテーブルは、ほかの TEMPORARY でないテーブルと同様に、すべてのクライアントで共有されます。

MEMORY テーブルへの DDL 操作

MEMORY テーブルを作成するには、CREATE TABLE ステートメントで ENGINE=MEMORY 句を指定します。

CREATE TABLE t (i INT) ENGINE = MEMORY;

エンジンの名前が表すように、MEMORY テーブルはメモリーに格納されます。デフォルトではハッシュインデックスを使用するため、単一値の検索には非常に高速であり、一時テーブルの作成には非常に役立ちます。ただし、サーバーがシャットダウンすると、MEMORY テーブルに格納されたすべての行が失われます。テーブルの定義はディスク上の .frm ファイルに格納されているため、テーブル自体は引き続き存在しますが、サーバーが再起動したときにテーブルは空になります。

この例は、MEMORY テーブルをどのように作成、使用、および削除できるかを示しています。

mysql> CREATE TABLE test ENGINE=MEMORY
    ->     SELECT ip,SUM(downloads) AS down
    ->     FROM log_table GROUP BY ip;
mysql> SELECT COUNT(ip),AVG(down) FROM test;
mysql> DROP TABLE test;

MEMORY テーブルの最大サイズは max_heap_table_size システム変数によって制限されます (デフォルト値は 16M バイト)。MEMORY テーブルに異なるサイズ制限を適用するには、この変数値を変更します。CREATE TABLE、それに続く ALTER TABLE または TRUNCATE TABLE の実質的な値は、テーブルの有効期限に使用される値です。サーバーを再起動しても、既存の MEMORY テーブルの最大サイズがグローバルの max_heap_table_size 値に設定されます。各テーブルのサイズをこのセクションの後半で説明するように設定できます。

インデックス

MEMORY ストレージエンジンは HASHBTREE の両方のインデックスをサポートしています。ここに示すように USING 句を追加することによりどちらであるかを指定できます。

CREATE TABLE lookup
    (id INT, INDEX USING HASH (id))
    ENGINE = MEMORY;
CREATE TABLE lookup
    (id INT, INDEX USING BTREE (id))
    ENGINE = MEMORY;

B ツリーとハッシュインデックスの一般的な特徴については、セクション8.3.1「MySQL のインデックスの使用の仕組み」を参照してください。

MEMORY テーブルでは、テーブル当たり最大で 64 個のインデックス、インデックス当たり 16 個のカラム、3072 バイトの最大キー長を持つことができます。

MEMORY テーブルのハッシュインデックスでキーの重複の程度が高いと (同じ値を含むインデックスエントリが多い)、キー値に影響を与えるテーブルへの更新処理とすべての削除処理の速度が大きく低下します。この低下の程度は重複の程度に比例します (または、インデックスカーディナリティーに反比例します)。BTREE インデックスを使用することで、この問題を回避できます。

MEMORY テーブルには、非一意キーを持つことができます。(これは、ハッシュインデックスの実装ではまれな特徴です。)

インデックスが付けられたカラムに NULL 値を含むことができます。

ユーザー作成の一時テーブル

MEMORY テーブルの内容はメモリーに格納されます。これは MEMORY テーブルが、クエリーの処理中にその場でサーバーが作成する内部一時テーブルと共有する特性です。ただし、2 つのタイプのテーブルには違いがあり、MEMORY テーブルはストレージ変換の影響を受けませんが、内部一時テーブルは次のような影響があります。

データのロード

MySQL サーバーの起動時に MEMORY テーブルを移入するには、--init-file オプションを使用できます。たとえば、このファイルで INSERT INTO ... SELECTLOAD DATA INFILE などのステートメントを実行することで、永続データソースからテーブルをロードできます。セクション5.1.3「サーバーコマンドオプション」およびセクション13.2.6「LOAD DATA INFILE 構文」を参照してください。

同時に別のセッションでアクセスされた MEMORY テーブルにデータをロードするため、MEMORYINSERT DELAYED をサポートしています。セクション13.2.5.2「INSERT DELAYED 構文」を参照してください。

MEMORY テーブルとレプリケーション

サーバーの MEMORY テーブルは、シャットダウンされて再起動されたときに空になります。サーバーがレプリケーションマスターの場合は、そのスレーブはこれらのテーブルが空になったことを認識しないため、スレーブのテーブルからデータを選択した場合に内容が古いことがわかります。マスターとスレーブの MEMORY テーブルの同期を取るため、MEMORY テーブルが起動してからマスター側で最初に使用されたときに、スレーブ側でもテーブルを空にするため、DELETE ステートメントがマスターのバイナリログに書かれます。スレーブでは、マスターの再起動とテーブルの最初の使用までの間は、テーブルのデータが古いままです。スレーブへの直接クエリーが古いデータを戻す可能性があるこの期間を避けるには、--init-file オプションを使用して起動時にマスター上の MEMORY テーブルを移入してください。

メモリー使用量の管理

サーバーには、同時に使用されるすべての MEMORY テーブルを保持するための十分なメモリーが必要です。

MEMORY テーブルから各行を削除しても、メモリーは再利用されません。テーブル全体が削除された場合にのみ、メモリーが再利用されます。削除された行に以前に使用されたメモリーは同じテーブル内の新しい行に再利用されます。MEMORY テーブルの内容が必要でなくなったときに、それが使用していたすべてのメモリーを解放するには、DELETE または TRUNCATE TABLE を実行してすべての行を削除するか、DROP TABLE を使用してテーブルを完全に削除します。削除された行が使用していたメモリーを解放するには、ALTER TABLE ENGINE=MEMORY を使用してテーブルを強制的に再作成します。

MEMORY テーブルで 1 つの行に必要なメモリーは、次の式で計算されます。

SUM_OVER_ALL_BTREE_KEYS(max_length_of_key + sizeof(char*) * 4)
+ SUM_OVER_ALL_HASH_KEYS(sizeof(char*) * 2)
+ ALIGN(length_of_row+1, sizeof(char*))

ALIGN() は行の長さを char ポインタサイズのちょうど倍数にするための切り上げ係数を表します。sizeof(char*) は 32 ビットマシンでは 4、64 ビットマシンでは 8 です。

前に述べたように、max_heap_table_size システム変数は MEMORY テーブルの最大サイズの制限値を設定します。各テーブルの最大サイズを制御するには、各テーブルを作成する前に、この変数のセッション値を設定します。(すべてのクライアントが作成した MEMORY テーブルに、グローバル max_heap_table_size 値を使用するのでなければ、この値を変更しないでください。)次は、2 つの MEMORY テーブル (最大サイズがそれぞれ 1M バイトと 2M バイト) を作成する例です。

mysql> SET max_heap_table_size = 1024*1024;
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE TABLE t1 (id INT, UNIQUE(id)) ENGINE = MEMORY;
Query OK, 0 rows affected (0.01 sec)

mysql> SET max_heap_table_size = 1024*1024*2;
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE TABLE t2 (id INT, UNIQUE(id)) ENGINE = MEMORY;
Query OK, 0 rows affected (0.00 sec)

両方のテーブルは、サーバーが再起動した場合、サーバーのグローバル max_heap_table_size 値に戻ります。

MEMORY テーブルに対して CREATE TABLE ステートメントの MAX_ROWS テーブルオプションを指定して、テーブルに格納する予定の行数に関するヒントを提供することもできます。これによって max_heap_table_size 値 (引き続き最大テーブルサイズの制約として機能) を超えてテーブルが拡大できなくなります。MAX_ROWS を使用できるだけの最大限の柔軟性を得るには、少なくとも各 MEMORY テーブルが拡大できる値程度に max_heap_table_size を設定してください。

追加のリソース

MEMORY ストレージエンジンに特化したフォーラムは、http://forums.mysql.com/list.php?92で参照できます。


User Comments
  Posted by Shelby Moore on January 16, 2005
I think the slowdown documented above is entirely unnecessary and the slowdown is not directly correlated to cardinality:

"...The degree of slowdown is proportional to the degree of duplication...You can use a BTREE index to avoid this problem."

Only a very simple "MTF" optimization needs to be made to the HEAP storage engine:

http://bugs.mysql.com/bug.php?id=7817

BTREEs are much slower than hashing (about 5 to 6 times at least), and are necessary only when non-equality (range) indexing is required. See the research paper quoted at above link for benchmarks.

So consider the above advice to use BTREEs to solve performance issues as incorrect because they are 5 - 6 times slower. BTREEs are a way to get 5 - 6 times slower performance than a correctly optimized HASH indexing. BTREEs may be faster in some cases than an *UN*optimized HASH index.

As for the issue of slowdown correlation to cardinality, see comment "16 Jan 9:32pm" in above link.
  Posted by Shelby Moore on January 16, 2005
Current HASH key implementation is unoptimized and much slower than it needs to be for the case where most queries result in non-match:

http://bugs.mysql.com/7936

In this case, it is possible that BTREE is faster until HASH is optimized.

  Posted by Stein Haugan on March 2, 2007
Insertion into HASH indexed columns is somewhat vulnerable to degenerate cases of "bad" data sets, which can cause insertion to be painfully slow (two orders of magnitude slower than a "normal" data set). See the examples (with suggestions for application-level fixes) below:

Create a table n:

mysql> create temporary table n (n int unsigned auto_increment primary key);

mysql> insert into n select NULL from SQ_SIMILAR2; -- a 1-million-row-table
Query OK, 1115156 rows affected (4.40 sec)
Records: 1115156 Duplicates: 0 Warnings: 0

Ok, now we have numbers 1-1e6 in table n.

mysql> create temporary table sq (sq int unsigned, key sq) engine memory;

Ok, now we're set. Look at the timings in the two insert statements:

mysql> insert into sq select floor(n/64*1024)*n from n;
Query OK, 1115156 rows affected, 65535 warnings (2.80 sec)
Records: 1115156 Duplicates: 0 Warnings: 1098773

mysql> truncate table sq;
Query OK, 0 rows affected (0.01 sec)

mysql> insert into sq select floor(n/(64*1024-1))*n from n;
Query OK, 1115156 rows affected (2 min 59.34 sec)
Records: 1115156 Duplicates: 0 Warnings: 0

In other words, a slow-down factor of 64! Obviously something weird is
going on that throws the adaptive cache algorithm to the ground!

Part of the problem can be solved by e.g. random reordering before
inserts (after truncating the table, of course):

mysql> insert into sq select floor(n/(64*1024-1))*n from n order by rand();
Query OK, 1115156 rows affected (52.64 sec)
Records: 1115156 Duplicates: 0 Warnings: 0

Now we're down to "only" a factor of about 20. But we can do even better:

mysql> insert into sq select floor(n/(64*1024-1))*n from n order by n desc;
Query OK, 1115156 rows affected (2.60 sec)
Records: 1115156 Duplicates: 0 Warnings: 0

Whee! Great.

Our actual data were a little different. The table SQ_SIMILAR2 contains
1.1 million non-unique numbers - about 180,000 distinct values between 1
and 1.1 million - in a, well, special [by accident] order. Here are some
timings (table sq is truncated before each insert):

mysql> insert into sq select SQ_SIMILAR2 from SQ_SIMILAR2;
Query OK, 1115156 rows affected (4 min 39.07 sec)
Records: 1115156 Duplicates: 0 Warnings: 0

I.e. a little worse than the test case above. Random ordering seems a tiny
bit worse. And ordering in ascending order is really, really bad:

mysql> insert into sq select SQ_SIMILAR2 from SQ_SIMILAR2 order by SQ_SIMILAR2;
Query OK, 1115156 rows affected (8 min 31.24 sec)
Records: 1115156 Duplicates: 0 Warnings: 0

Yikes, a slow-down factor of 182 compared to the floor(n/64*1024)*n
example above. Sorting in descending order gets back within the realm of
the reasonable again:

mysql> insert into sq select SQ_SIMILAR2 from SQ_SIMILAR2 order by SQ_SIMILAR2 $
Query OK, 1115156 rows affected (4.54 sec)
Records: 1115156 Duplicates: 0 Warnings: 0

But with non-unique data, can you do better? Try this:

mysql> insert into sq select distinct SQ_SIMILAR2 from SQ_SIMILAR2;
Query OK, 181272 rows affected (0.61 sec)
Records: 181272 Duplicates: 0 Warnings: 0

mysql> insert into sq select SQ_SIMILAR2 from SQ_SIMILAR2;
Query OK, 1115156 rows affected (1.50 sec)
Records: 1115156 Duplicates: 0 Warnings: 0

Alltogether only 2.11 sec, half the time of the descending sort order,
although further table manipulations are necessary to delete the spurious
duplicates that have been created.

  Posted by Eric Walker on December 6, 2007
When joining a column in a MEMORY table against one in an InnoDB table, the kind of indexes on the columns is important.

In my case, when a column on a MEMORY table was of type HASH and the corresponding column in the InnoDB table of type BTREE, the query optimizer was not able to make use of the indexes and queries were taking a long time. A fix in this instance was to convert the default HASH index on the MEMORY table column to BTREE.
Sign Up Login You must be logged in to post a comment.