【发布时间】:2021-09-04 06:08:07
【问题描述】:
背景
-
users表有 2k 行 -
relationships表有 150 万行 -
posts表有 200 万行 - 使用 mysql 版本 5.7.34
users 的结构:
CREATE TABLE `users` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`email` varchar(255) NOT NULL DEFAULT '',
`first_name` varchar(255) NOT NULL DEFAULT '',
`last_name` varchar(255) NOT NULL DEFAULT '',
`password` varchar(255) NOT NULL DEFAULT '',
`active` tinyint(1) NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB AUTO_INCREMENT=3263 DEFAULT CHARSET=utf8
relationships 的结构:
CREATE TABLE `relationships` (
`user_id` int(11) unsigned NOT NULL,
`is_following_user_id` int(11) unsigned NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY `user_id` (`user_id`,`is_following_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
posts 的结构:
CREATE TABLE `posts` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) unsigned NOT NULL,
`parent_post_id` int(11) DEFAULT NULL,
`content` varchar(255) DEFAULT '',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
CONSTRAINT `users` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2412061 DEFAULT CHARSET=utf8
注意:用户 922 没有任何关系或帖子,因此查询会根据需要执行完整的索引和/或表扫描。
这个查询耗时 0.5ms:
# 0.5ms
select * from posts where user_id in (
select id from users inner join relationships
on users.id = relationships.is_following_user_id
where relationships.user_id = 922
);
这个查询需要 500 毫秒:
# 500ms
select * from posts where user_id in (
select id from users inner join relationships
on users.id = relationships.is_following_user_id
where relationships.user_id = 922
)
or user_id = 922;
很明显,对于第二个查询,它识别出与第一个查询(users.user_id) 相同的索引,但在第二个查询中,根据解释输出,它特别避免使用它(key = NULL)。
这个查询需要 2.3 秒:
# 2.3 seconds
select * from posts where user_id in (
select id from users inner join relationships
on users.id = relationships.is_following_user_id
where relationships.user_id = 922
union all
select 922
);
问题:
- 为什么查询 #2 不像查询 #1 那样使用
users.user_id索引? - 为什么查询 #3 这么慢,而且还没有使用
users.user_id索引?
【问题讨论】:
-
为了完整起见,我建议尝试第四个查询,合并两个
select * from posts的结果:一个带有 where 子查询检查,另一个用于where user_id = 922检查。 -
除了你的问题,你为什么不把你的第一个查询简化为
SELECT * FROM posts INNER JOIN relationships ON posts.user_id = relationships.is_following_user_id WHERE relationships.user_id = 922 -
顺便说一句,(user_id,is_following_user_id) 是主要的
-
底线是 MySQL 的优化器在处理
IN ( SELECT ... )方面做得很糟糕;躲开它!几乎总是将重新表述为JOIN有帮助。 (有时EXISTS( SELECT ... )可以很好地工作。)
标签: mysql sql performance indexing subquery