包含blob的表的mysql查询速度取决于文件系统缓存

per*_*lis 25 mysql performance caching blob

我有一个大约120k行的表,其中包含一个带有BLOB的字段(每个条目的大小不超过1MB,通常要少得多).我的问题是每当我运行查询询问此表上的任何列(包括BLOB)时,如果文件系统缓存为空,则需要大约40'才能完成.同一个表上的所有后续查询都需要少于1''(从命令行客户端,在服务器本身上进行测试).查询中返回的行数从空集到60k +不等

我已经删除了查询缓存,因此它与它无关.该表是myisam,但我也尝试将其更改为innodb(并设置ROW_FORMAT = COMPACT),但没有任何运气.

如果我删除BLOB列,查询总是很快.

所以我假设服务器从磁盘(或其中的一部分)读取blob,文件系统缓存它们.问题是在流量高且内存有限的服务器上,文件系统缓存每隔一段时间刷新一次,因此这个特定的查询一直给我带来麻烦.

所以我的问题是,有没有办法大幅加快速度,而无需从表中删除blob列?

这里有两个示例查询,一个接一个地运行,以及解释,索引和表定义:

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)
Run Code Online (Sandbox Code Playgroud)

我最初快速发布这个关于mysql查询的速度很快,但我现在有更多的信息,所以我重新发布作为一个不同的问题我也在mysql论坛上发布了这个,但我没有收到回复

一如既往地感谢您

Mar*_*ski 19

MySQL中BLOB(= TEXT)存储的设计似乎完全有缺陷且反直觉.我跑了几次同样的问题,无法找到任何权威的解释.我最终发现的最详细的分析是2010年的这篇文章:http://www.mysqlperformanceblog.com/2010/02/09/blob-storage-in-innodb/

一般的信念和期望是BLOB/TEXT存储在主行存储之外(例如,参见这个答案).但这不是正确的.这里有几个问题(我基于上面给出的文章):

  1. 如果BLOB项的大小是几KB,则它直接包含在行数据中.因此,即使您只选择非BLOB列,引擎仍然必须从磁盘加载所有BLOB.比如说,你有1M行,每行包含100字节的非blob数据和5000字节的blob数据.您选择所有非blob列并期望MySQL从磁盘读取每行100-120字节,总共100-120 MB(BLOB地址+20).但是,实际情况是MySQL将所有BLOB存储在与行相同的磁盘块中,因此即使不使用它们也必须一起读取,因此从磁盘读取的数据大小约为5100 MB = 5 GB - 这是50倍以上,你会期望和手段慢50倍的查询执行.

    当然,这种设计有一个优点:当你需要所有的列,包括blob one时,当blob与行一起存储时,SELECT查询比在外部存储时更快:你避免(有时)每行1次额外的页面访问.但是,这不是BLOB的典型用例,并且不应针对此情况优化数据库引擎.如果您的数据非常小以至于它可以放在一行中并且您可以在每个查询中加载它,无论是否需要 - 那么您将使用VARCHAR类型而不是BLOB/TEXT.

  2. 即使由于某种原因(长行或长blob)BLOB值存储在外部,其768字节前缀仍保留在行本身中.让我们看一个前面的例子:每行有100个字节的非blob数据,但现在blob列包含每个1 MB的项目,所以它们必须保存在外部.非blob列的SELECT必须每行读取大约800个字节(非blob + blob前缀),而不是100-120 - 这也是你预期的7倍大的磁盘传输,并且查询执行速度慢7倍.

  3. 外部BLOB存储在磁盘空间使用方面无效:它以16 KB的块分配空间,而单个块不能容纳多个项目,因此如果您的blob很小并且例如每个8 KB,则分配的实际空间是两次那么大.

我希望这个设计有一天会得到修复:MySQL会将所有blob(无论大小)存储在外部存储中,而不会在DB中保留任何前缀,外部存储分配对于所有大小的项目都是有效的.在此之前,分离出 BLOB/TEXT列似乎是唯一合理的解决方案 - 分离到另一个表或文件系统(每个BLOB值保存为文件).


小智 17

我有一段时间正在研究这个问题.许多人建议在单独的表中使用只有一个主键的blob,并将blob元数据存储在另一个表中,并使用外键存储到blob表中.有了这个,性能将大大提高.