我有一个大约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)
我最初是在mysql query slow at first fast afterwards上发布的,但我现在有更多信息,所以我将其作为一个不同的问题重新发布 我也在mysql forum发布了这个,但我还没有收到回复
一如既往地感谢
答案 0 :(得分:18)
MySQL中BLOB(= TEXT)存储的设计似乎完全有缺陷且反直觉。我跑了几次同样的问题,无法找到任何权威的解释。我最终找到的最详细的分析是2010年的这篇文章:http://www.mysqlperformanceblog.com/2010/02/09/blob-storage-in-innodb/
普遍的看法和期望是BLOB / TEXT存储在主行存储之外(例如,见this answer)。但这不是正确的。这里有几个问题(我基于上面给出的文章):
如果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。
即使由于某种原因(长行或长blob)BLOB值存储在外部,其 768字节前缀仍保留在行本身中。让我们看一个前面的例子:每行有100个字节的非blob数据,但现在blob列保存每个1 MB的项目,因此它们必须保存在外部。非blob列的SELECT必须读取每行大约800个字节(非blob + blob前缀),而不是100-120 - 这又是 7倍大磁盘传输比你预期的, 7x较慢查询执行。
外部BLOB存储在磁盘空间使用方面无效:它以16 KB的块分配空间而单个块不能容纳多个项目,所以如果你的blob很小并且每个占用8 KB,分配的实际空间两次那么大。
我希望这个设计有一天会得到修复:MySQL会将所有blob(无论大小)存储在外部存储中,而不会在DB中保留任何前缀,外部存储分配对于所有大小的项目都是有效的。在此之前,分离出 BLOB / TEXT列似乎是唯一合理的解决方案 - 分离到另一个表或文件系统(每个BLOB值保存为文件)。
答案 1 :(得分:16)
我正在研究这个问题一段时间。许多人建议在单独的表中使用只有一个主键的blob,并将blob元数据存储在另一个表中,并使用外键存储到blob表中。有了这个,性能将大大提高。
答案 2 :(得分:2)
在两个相关列上添加复合索引应该允许执行这些查询而无需直接访问表数据。
CREATE INDEX `IX_score_status` ON `completed_tests` (`score`, `status`);
如果您能够切换到MariaDB,那么您可以充分利用表消除优化。这将允许您将BLOB字段拆分到它自己的表中,并使用视图使用LEFT JOIN重新创建现有的表结构。这样,只有在执行查询明确需要时才会访问BLOB数据。
答案 3 :(得分:-1)
只需将索引或索引添加到WHERE查询带有blob的表后使用的字段。
e.g。你有2个包含这些字段的表
users : USERID, NAME, ...
userphotos : BLOBID, BLOB, USERNO, ...
select * from userphotos where USERNO=123456;
Normaly这很好用。当你有许多大图像(例如BLOB,MEDIUMBLOB或LONGBLOB总共超过5GB)时,这将花费很多时间(超过几分钟),而BLOBID是主键。
如果在WHERE子句中没有关于BLOB表的字段的索引,MySQL会以某种方式搜索包括图像在内的整个数据。当您的数据越来越大,需要花费很多时间。如果您为字段USERNO创建索引,这将加速您的数据库,它将与整个数据的大小无关。
<强>解决方案:强>
**Add Index to the USERNO at userphotos**
作为您问题的答案,您应该为ct.status
创建索引