在 SELECT 中包含 category.name,导致查询执行 Using 索引;使用临时的;使用文件排序,扫描的行也不受 LIMIT 0,1 的影响。
CREATE TABLE `item` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `category` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `feature` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`item_id` INT(10) UNSIGNED DEFAULT NULL,
`category_id` INT(10) UNSIGNED DEFAULT NULL,
`start_date` DATE DEFAULT NULL,
`created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `tbl_feature_id_item_id_foreign` (`item_id`),
KEY `tbl_feature_id_category_id_foreign` (`category_id`),
CONSTRAINT `tbl_feature_id_item_id_foreign` FOREIGN KEY (`item_id`) REFERENCES `item` (`id`),
CONSTRAINT `tbl_feature_id_category_id_foreign` FOREIGN KEY (`category_id`) REFERENCES `category` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `item` (`id`) VALUES (1),(2),(3),(4),(5);
INSERT INTO `category` (`id`, `name`, `created_at`, `updated_at`)
VALUES
(1, 'a', '2016-12-02 22:49:46', NULL),
(2, 'b', '2016-12-02 22:49:48', NULL)
;
INSERT INTO `feature` (`id`, `item_id`, `category_id`, `start_date`, `created_at`, `updated_at`)
VALUES
(1, 1, 1, '2016-12-01', NOW(), NOW()),
(2, 1, 2, '2016-12-02', NOW(), NOW()),
(3, 2, 1, '2016-12-01', NOW(), NOW()),
(4, 2, 2, '2016-12-02', NOW(), NOW()),
(5, 3, 1, '2016-12-01', NOW(), NOW()),
(6, 3, 2, '2016-12-02', NOW(), NOW()),
(7, 4, 1, '2016-12-01', NOW(), NOW()),
(8, 4, 2, '2016-12-02', NOW(), NOW()),
(9, 5, 1, '2016-12-01', NOW(), NOW()),
(10, 5, 2, '2016-12-02', NOW(), NOW())
;
EXPLAIN EXTENDED
SELECT
item.id
, feature.id
, category.id
, category.name -- Is the cause of the temporary table; file-sort;
FROM
item
LEFT JOIN feature ON (
feature.item_id = item.id
AND feature.start_date = (
SELECT
MAX(start_date) AS start_date
FROM
feature
WHERE
feature.item_id = item.id
)
)
LEFT JOIN category ON (
category.id = feature.category_id
)
ORDER BY item.id DESC -- or ASC
LIMIT 0, 1 -- Is ignored in the table scan
;
MySQL 5.7.16
我相信可能是因为排序完成后需要访问源表。因为文件排序是在模式 2 中执行的
http://s.petrunia.net/blog/?p=24
模式 2:对对进行排序并生成一系列 rowid,可用于按所需顺序获取源表的行(但这实际上是按随机顺序命中表,速度不是很快)
向 category.name 添加索引确实可以解决问题。这是因为 category.name 现在是索引的一部分了吗?
任何人都可以确认,并可能提供比添加索引更好的解决方案,因为实际上,它并没有解决问题,因为时间点构造是针对多个其他功能重复的,即 feature_a、feature_b。
为什么使用索引;使用临时的;使用文件排序执行?
为什么 LIMIT 0,1 不影响扫描的行?
临时表是因为您在连接中有一个依赖子查询来计算 MAX(startdate)。
我能够通过添加索引来改进 EXPLAIN:
并更改查询以避免依赖子查询:
f1 和 f2 之间的这种连接旨在确保 f1 具有其各自 item_id 的最大 start_date。如果到 f2 的 LEFT JOIN 没有找到具有相同 item_id 和更大 start_date 的其他行,则 f1 必须具有最大的 start_date。
这是我得到的解释:
我在我的 Macbook 上测试了 MySQL 8.0.0-dmr。
复合覆盖
INDEX(item_id, start_date)
将提高第一个子查询的性能。“使用索引”意味着索引是“覆盖”的。“使用索引条件”是完全不同的——它指的是 ICP(索引条件下推)。
LIMIT 1
仅在某些情况下被折叠到优化中。一种“一般”情况是优化器可以使用所有、WHERE
和。几乎所有其他情况都会导致扫描多行,而我会导致一个(或多个)临时表和排序。GROUP BY
ORDER BY
1
索引食谱。
MySQL 没有“rowids”。这样
PRIMARY KEY
的服务。