select 中的 longtext 使查询速度极慢,即使未在 where 子句和空结果集 (MySQL) 中使用

Gra*_*yle 6 mysql

只要我在 select 子句中包含“longtext”类型,查询时间就会从 8 秒变为 3 分钟(Amazon RDS t2.small)。where 子句中不使用长文本,结果集为空。见下文:

mysql> select id from mbp_process where errorAcknowledged='N' and (exitCode != 0 or exitCode is null);
Empty set (8.03 sec)

mysql> select id, stdoutContents from mbp_process where errorAcknowledged='N' and (exitCode != 0 or exitCode is null);
Empty set (3 min 43.36 sec)
Run Code Online (Sandbox Code Playgroud)

令我难以置信的是,通过主键请求 longtext 列很快:

select stdoutContents from mbp_process where id = 49213;
...
1 row in set (0.00 sec)
Run Code Online (Sandbox Code Playgroud)

为什么是这样?在我的裸机服务器上效果不太明显:查询从 0.2s 减慢到 1:05m。

这是“select id from...”查询的解释:

+----+-------------+-------------+------+-----------------------------------------------------+----------------------------+---------+-------+-------+------------------------------------+
| id | select_type | table       | type | possible_keys                                       | key                        | key_len | ref   | rows  | Extra                              |
+----+-------------+-------------+------+-----------------------------------------------------+----------------------------+---------+-------+-------+------------------------------------+
|  1 | SIMPLE      | mbp_process | ref  | idx_mbp_process_exitCode,idx_mbp_process_errorAcked | idx_mbp_process_errorAcked | 2       | const | 22551 | Using index condition; Using where |
+----+-------------+-------------+------+-----------------------------------------------------+----------------------------+---------+-------+-------+------------------------------------+
Run Code Online (Sandbox Code Playgroud)

这是来自“select id, stdoutContents from ...”查询的解释:

+----+-------------+-------------+------+-----------------------------------------------------+----------------------------+---------+-------+-------+------------------------------------+
| id | select_type | table       | type | possible_keys                                       | key                        | key_len | ref   | rows  | Extra                              |
+----+-------------+-------------+------+-----------------------------------------------------+----------------------------+---------+-------+-------+------------------------------------+
|  1 | SIMPLE      | mbp_process | ref  | idx_mbp_process_exitCode,idx_mbp_process_errorAcked | idx_mbp_process_errorAcked | 2       | const | 22552 | Using index condition; Using where |
+----+-------------+-------------+------+-----------------------------------------------------+----------------------------+---------+-------+-------+------------------------------------+
Run Code Online (Sandbox Code Playgroud)

它们是相同的。

这是“SHOW CREATE TABLE mbp_process”中的创建表语句:

CREATE TABLE `mbp_process` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `command` varchar(1000) DEFAULT NULL,
  `pid` varchar(45) DEFAULT NULL,
  `state` varchar(45) NOT NULL,
  `exitCode` int(11) DEFAULT NULL,
  `stdoutContents` longtext,
  `stdoutTruncated` char(1) DEFAULT NULL,
  `stdoutFilename` varchar(200) DEFAULT NULL,
  `stderrContents` longtext,
  `stderrTruncated` char(1) DEFAULT NULL,
  `stderrFilename` varchar(200) DEFAULT NULL,
  `majorProgress` varchar(45) DEFAULT NULL,
  `minorProgress` varchar(45) DEFAULT NULL,
  `startTime` datetime DEFAULT NULL,
  `endTime` datetime DEFAULT NULL,
  `errorAcknowledged` char(1) DEFAULT 'N',
  `errorComments` text,
  `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_mbp_process_command` (`command`(767)),
  KEY `idx_mbp_process_exitCode` (`exitCode`),
  KEY `idx_mbp_process_state` (`state`),
  KEY `idx_mbp_process_errorAcked` (`errorAcknowledged`)
) ENGINE=InnoDB AUTO_INCREMENT=50184 DEFAULT CHARSET=latin1
Run Code Online (Sandbox Code Playgroud)

选择要包含的另一列不会减慢查询速度:

mysql> select id, created from mbp_process where errorAcknowledged='N' and (exitCode != 0 or exitCode is null);
Empty set (1.69 sec)
Run Code Online (Sandbox Code Playgroud)

这很奇怪:如果我包含“stderrContents”,查询就会很快。这也是一个长文本列,尽管它通常包含的数据要少得多。但是,我不是要求 MySQL 检查列的内容,结果集是空的,那么为什么“stdoutContents”很慢?

mysql> select id, stderrContents from mbp_process where errorAcknowledged='N' and (exitCode != 0 or exitCode is null);
Empty set (0.57 sec)
Run Code Online (Sandbox Code Playgroud)

Ric*_*mes 4

PRIMARY KEY(id)说它与数据聚集在一起。这不是问题。也没有尝试使用二级索引。这就是正在发生的事情。

在InnoDB中,通常所有列都位于BTree中由PK索引的主键旁边。然而,“大”列被放置在其他地方。正如您所描述的,最多大约 8KB 的行被保留在一起。大列位于其自己的 16KB 块中。这些列包括任何 TEXT/BLOB,甚至长 VARCHAR/VARBINARY 列。(详细信息因 innodb_file_format 和其他一些因素而异。例如,大列的前 767 个字节可能会保留为短列。)

因此,当选择排除此类大列的列时,查询将避免获取这些额外的块并且相对较快。听起来 stdoutContents 真的很大(需要多个 16KB 块)?

亚马逊与裸机:如果我没记错的话,亚马逊将数据存储在类似 SAN 的系统上,而不是同一个盒子上。

我看到的另一件事......说它EXPLAIN正在使用这个辅助密钥

KEY `idx_mbp_process_errorAcked` (`errorAcknowledged`)
Run Code Online (Sandbox Code Playgroud)

每个辅助密钥都隐式包含 PK ( id)。所以,处理过程是这样的:

  • 钻取第一个条目的辅助键errorAcknowledged='N'
  • 在 BTree 中向前扫描。这是最有效的步骤。每个 16KB 块可能有超过 100 个“行”。
  • 对于其中每一个,使用id来访问“数据”BTree。(每行 1 个块,如果缓存得很好,希望更少)
  • 检查 WHERE 子句的其余部分:and (exitCode != 0 or exitCode is null)
  • 如果该行仍然有趣,请获取所需的本地列(id、created,也许还有stderrContents),然后
  • 进入 stdoutContents 的“大”存储(如果您需要的话)。这可能不会被缓存,并且可能涉及许多磁盘命中。

我希望这能解释一切。如果您需要进一步说明,请告诉我。