web-dev-qa-db-ja.com

BLOBを含むテーブルでのmysqlクエリの速度は、ファイルシステムのキャッシュに依存します

約12万行のテーブルがあり、そこにはBLOBを含むフィールドが含まれています(各エントリのサイズは1MB以下、通常ははるかに小さい)。私の問題は、このテーブルの列を尋ねるクエリを実行するたびに(notBLOBを含む)、ファイルシステムキャッシュが空の場合、完了するには約40インチ。同じテーブルに対する後続のすべてのクエリには、1インチ未満が必要です(サーバー自体で、コマンドラインクライアントからテストします)。クエリで返される行数は、空のセットから60k +までさまざまです。

クエリキャッシュを削除したので、それとは何の関係もありません。テーブルはmyisamですが、innodbに変更しようとしました(そしてROW_FORMAT = COMPACTを設定しました)が、運がありませんでした。

BLOB列を削除すると、クエリは常に高速になります。

したがって、サーバーがディスク(またはその一部)からblobを読み取り、ファイルシステムがそれらをキャッシュすると仮定します。問題は、トラフィックが多くメモリが限られているサーバーでは、ファイルシステムキャッシュが時々更新されるため、この特定のクエリが問題を引き起こし続けることです。

だから私の質問は、テーブルからblob列を削除せずに、物事を大幅にスピードアップする方法はありますか?

これは、explain、インデックス、およびテーブル定義とともに、次々に実行される2つのクエリ例です。

mysql> SELECT ct.score FROM completed_tests ct where ct.status != 'deleted' and ct.status != 'failed' and score < 100;
Empty set (48.21 sec)
mysql> SELECT ct.score FROM completed_tests ct where ct.status != 'deleted' and ct.status != 'failed' and score < 99;
Empty set (1.16 sec)

mysql> explain SELECT ct.score FROM completed_tests ct where ct.status != 'deleted' and ct.status != 'failed' and score < 99;
+----+-------------+-------+-------+---------------+--------+---------+------+-------+-------------+
| id | select_type | table | type  | possible_keys | key    | key_len | ref  | rows  | Extra       |
+----+-------------+-------+-------+---------------+--------+---------+------+-------+-------------+
|  1 | SIMPLE      | ct    | range | status,score  | status | 768     | NULL | 82096 | Using where |
+----+-------------+-------+-------+---------------+--------+---------+------+-------+-------------+
1 row in set (0.00 sec)


mysql> show indexes from completed_tests;
+-----------------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table           | Non_unique | Key_name    | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-----------------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| completed_tests |          0 | PRIMARY     |            1 | id          | A         |      583938 |     NULL | NULL   |      | BTREE      |         |
| completed_tests |          1 | users_login |            1 | users_LOGIN | A         |       11449 |     NULL | NULL   | YES  | BTREE      |         |
| completed_tests |          1 | tests_ID    |            1 | tests_ID    | A         |         140 |     NULL | NULL   |      | BTREE      |         |
| completed_tests |          1 | status      |            1 | status      | A         |           3 |     NULL | NULL   | YES  | BTREE      |         |
| completed_tests |          1 | timestamp   |            1 | timestamp   | A         |      291969 |     NULL | NULL   |      | BTREE      |         |
| completed_tests |          1 | archive     |            1 | archive     | A         |           1 |     NULL | NULL   |      | BTREE      |         |
| completed_tests |          1 | score       |            1 | score       | A         |         783 |     NULL | NULL   | YES  | BTREE      |         |
| completed_tests |          1 | pending     |            1 | pending     | A         |           1 |     NULL | NULL   |      | BTREE      |         |
+-----------------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

mysql> show create table completed_tests;
+-----------------+--------------------------------------
| Table           | Create Table                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
+-----------------+--------------------------------------
| completed_tests | CREATE TABLE `completed_tests` (
  `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
  `users_LOGIN` varchar(100) DEFAULT NULL,
  `tests_ID` mediumint(8) unsigned NOT NULL DEFAULT '0',
  `test` longblob,
  `status` varchar(255) DEFAULT NULL,
  `timestamp` int(10) unsigned NOT NULL DEFAULT '0',
  `archive` tinyint(1) NOT NULL DEFAULT '0',
  `time_start` int(10) unsigned DEFAULT NULL,
  `time_end` int(10) unsigned DEFAULT NULL,
  `time_spent` int(10) unsigned DEFAULT NULL,
  `score` float DEFAULT NULL,
  `pending` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `users_login` (`users_LOGIN`),
  KEY `tests_ID` (`tests_ID`),
  KEY `status` (`status`),
  KEY `timestamp` (`timestamp`),
  KEY `archive` (`archive`),
  KEY `score` (`score`),
  KEY `pending` (`pending`)
) ENGINE=InnoDB AUTO_INCREMENT=117996 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED
1 row in set (0.00 sec)

私はもともとこれを mysqlクエリは最初は遅いが後で速い に投稿しましたが、今はより多くの情報があるので、別の質問として再投稿します mysqlフォーラム にも投稿しましたが、私は返事がない

いつものように事前に感謝します

27
periklis

私はしばらくの間この問題について研究をしていました。多くの人は、別のテーブルに主キーが1つしかないblobを使用し、blobテーブルへの外部キーを使用してblobメタデータを別のテーブルに格納することを推奨しています。これにより、パフォーマンスが大幅に向上します。

16
HesamDadafarin

MySQLのBLOB(= TEXT)ストレージの設計には完全に欠陥があり、直感に反しているようです。私は同じ問題に数回遭遇し、信頼できる説明を見つけることができませんでした。私が最終的に見つけた最も詳細な分析は、2010年のこの投稿です: http://www.mysqlperformanceblog.com/2010/02/09/blob-storage-in-innodb/

一般的な信念と期待は、BLOB/TEXTがメイン行ストレージの外部に格納されることです(たとえば、 この回答 を参照)。ただし、これは正しくありません。ここにはいくつかの問題があります(私は上記の記事に基づいています):

  1. BLOBアイテムのサイズが数KBの場合、行データに直接含まれます。したがって、非BLOB列のみを選択した場合でも、エンジンはすべてのBLOBをディスクからロードする必要があります。たとえば、100バイトの非blobデータと5000バイトのblobデータを含む100万行があるとします。すべての非blob列を選択し、MySQLがディスクから1行あたり約100〜120バイト、つまり合計100〜120 MBを読み取ることを期待します( +20(BLOBアドレスの場合)。ただし、実際には、MySQLはすべてのBLOBを行と同じディスクブロックに格納するため、すべてを一緒に読み取る必要があります使用しない場合でもしたがって、ディスクから読み取られるデータのサイズは約5100MBです。 =5 GB-これは50倍予想よりも多く、 50倍遅いクエリの実行を意味します。

    もちろん、この設計には利点があります。BLOBを含むすべての列が必要な場合、BLOBが外部に保存される場合よりも、行とともに保存される場合のSELECTクエリの方が高速です。(場合によっては)行ごとに1つの追加ページアクセスを回避できます。ただし、これはBLOBの一般的な使用例ではなく、DBエンジンをこの場合に最適化しないでください。データが小さすぎて行に収まらず、必要かどうかに関係なくすべてのクエリにデータをロードしても問題がない場合は、BLOB/TEXTの代わりにVARCHARタイプを使用します。

  2. 何らかの理由(長い行または長いblob)でBLOB値が外部に格納されている場合でも、その768バイトのプレフィックスは行自体に保持されます。前の例を見てみましょう。各行に100バイトの非blobデータがありますが、blob列にはそれぞれ1 MBのアイテムが保持されるため、外部に保持する必要があります。非blob列のSELECTは、100〜120ではなく1行あたり約800バイト(非blob + blobプレフィックス)を読み取る必要があります-これも7倍大きい予想よりもディスク転送が遅く、クエリの実行が7倍遅くなります

  3. 外部BLOBストレージは、ディスクスペースの使用に効果がありません。16KBのブロックにスペースを割り当て、単一のブロックに複数のアイテムを保持することはできません。したがって、BLOBが小さく、たとえばそれぞれ8 KBを使用する場合、割り当てられる実際のスペースは2回その大きさ。

この設計がいつか修正されることを願っています。MySQLはすべてのblob(大小)を外部ストレージに格納し、プレフィックスをDBに保持せず、外部ストレージの割り当てはすべてのサイズのアイテムに対して効率的です。これが発生する前に、分離BLOB/TEXT列が唯一の合理的な解決策のようです-別のテーブルまたはファイルシステムに分離します(各BLOB値はファイル)。

18

関連する2つの列に複合インデックスを追加すると、テーブルデータに直接アクセスせずにこれらのクエリを実行できるようになります。

CREATE INDEX `IX_score_status` ON `completed_tests` (`score`, `status`);

MariaDBに切り替えることができる場合は、テーブル消去の最適化を最大限に活用できます。これにより、BLOBフィールドを独自のテーブルに分割し、ビューを使用して、LEFTJOINを使用して既存のテーブル構造を再作成できます。このように、クエリの実行に明示的に必要な場合にのみ、BLOBデータにアクセスします。

2
nnichols