“SELECT my_table.id”比“SELECT my_table.*”慢

iva*_*van 5 mysql innodb performance query-performance

使用 InnoDB 运行 MySQL:

我有一个SELECT wide_table.*要优化的查询,SELECT wide_table.id因为这就是调用代码所需的全部内容。对其进行测试,我发现 with 的执行时间*比 with 快id(尽管“精制”版本通过网络传输结果的时间更快)。

为什么会这样?

查询(更改名称以保护无辜者)是:

SELECT
    `things`.*
FROM
    `things`
WHERE
    `things`.`active` = 1
        AND (owner_id IS NOT NULL
        AND owner_id > 0)
        AND ((`things`.`status` IN (0 , 1)
        OR `things`.`status` IS NULL))
        AND (date < '2015-07-11 00:00:00');
Run Code Online (Sandbox Code Playgroud)

有一个复合索引active并且date,这是在两个版本中使用。

作为参考,输出SHOW CREATE TABLE(省略了不相关的列):

CREATE TABLE `things` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `active` tinyint(1) DEFAULT '0',
  `date` datetime DEFAULT NULL,
  `owner_id` int(11) DEFAULT '0',
  `status` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_things_on_active_and_date` (`active`,`date`),
  KEY `index_things_on_date` (`date`),
  KEY `index_things_on_owner_id` (`owner_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1862 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
Run Code Online (Sandbox Code Playgroud)

限制

玩了一段时间后,我发现比较受到对查询施加的限制的影响。使用大限制(>200)时,该*版本报告更快的执行时间。随着限制降低 <200,id版本占上风。仍然不知道该怎么做...

解释

EXPLAIN在两个版本的查询上运行会产生相同的输出,带有 select_type:SIMPLE和 key: index_things_on_active_and_date

Rol*_*DBA 2

SELECT *您所说的和 的EXPLAIN 计划SELECT id是相同的,将按访问的行数分隔。如何访问这些行?通过index_things_on_active_and_date索引。EXPLAIN 中的SIMPLE表示它是扫描。在这两种情况下,它都是基于active=1和 的索引扫描date < '2015-07-11 00:00:00'

索引扫描是如何发生的?

  • 由于所选索引具有activedate,因此范围扫描将从两列进行。
  • 看一下WHERE条款。您正在检索owner_idstatus检查这些值。这需要您访问整行。
    • SELECT *意味着您将整行作为结果集的一部分
    • SELECT id意味着您将 id 作为结果集的一部分

这通向哪里?

  • 在这两种情况下,您都在读取整行以检查不在索引中的另外两列。
  • 每个查询的结果集要么是整行,要么是单列(id)。
  • 创建较小的结果集必须花费一些额外的时间。为什么 ?总有一天,较长的id值结果会花费更长的时间来构建您阅读的整行。您自己进行了实证测试并发现了以下内容:
    • < 200 rows->SELECT id更快
    • > 200 rows->SELECT *更快
    • = 200 rows->SELECT idSELECT *大致相同

关于 InnoDB 还有一些你需要了解的事情

这告诉您索引index_things_on_active_and_date实际上有三列: 1) active, 2) date, 3) id

您可能会说:为什么SELECT *跑步更好SELECT id?(这是最初的问题)这又回到了我所说的:WHERE 子句导致查询优化器检查非索引列statusowner_id. 您正在创建额外的工作,检查索引条目以及索引行中的某些内容。

如果你创建这个索引

ALTER TABLE things
    ADD INDEX index_everything_and_kitchen_sink
    (active,date,owner_id,status)
;
Run Code Online (Sandbox Code Playgroud)

SELECT id并运行这两个查询,那么无论您访问多少行,您都会看到优势。为什么 ?因为WHERE 子句中的所有列仅从索引中检查。这种类型的索引称为覆盖索引。

以下是一些关于覆盖索引的好链接

我在一些答案中提到了这些链接: