【发布时间】:2022-10-15 05:10:07
【问题描述】:
我有一个用户具有“bio”字段和“n:n”关系的表通过追随者桌子。因此每个用户 U 可以关注许多其他用户。
问题:我的用户搜索查询非常慢。
观察:
- 所有查询都获得前 20 个搜索结果 (
limit 20) - 搜索简历中包含“创始人”的用户需要 0.3 秒
- 搜索关注X的用户,耗时0.03s
- 搜索在个人简介中有“创始人”并关注 X 的用户,需要 118 秒!!!
询问:
这是搜索两个过滤器的最终查询:
select distinct `twitter_user`.`id`
from `twitter_user`
join `twitter_user_follower`
on (
`twitter_user_follower`.`follower_twitter_user_id` =
`twitter_user`.`id`
and `twitter_user_follower`.`twitter_user_id` = 4899565692
and `twitter_user_follower`.`follower_download_id` = 7064
)
where MATCH(twitter_user.description) AGAINST('founder')
limit 20 offset 0
表定义
CREATE TABLE `twitter_user` (
`id` bigint NOT NULL,
`name` varchar(128) NOT NULL,
`email` varchar(128) DEFAULT NULL,
`screen_name` varchar(128) DEFAULT NULL,
`location` varchar(256) DEFAULT NULL,
`description` varchar(512) DEFAULT NULL,
`url` varchar(256) DEFAULT NULL,
`is_protected` bit(1) DEFAULT NULL,
`followers_count` int DEFAULT NULL,
`is_verified` bit(1) DEFAULT NULL,
`friends_count` int DEFAULT NULL,
`created_at` bigint DEFAULT NULL,
`favourites_count` int DEFAULT NULL,
`utc_offset` int DEFAULT NULL,
`time_zone` varchar(128) DEFAULT NULL,
`statuses_count` int DEFAULT NULL,
`profile_image_url` varchar(512) DEFAULT NULL,
`internal_json` json DEFAULT NULL,
`row_timestamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `twitter_user_username_index` (`screen_name`),
KEY `twitter_user_ts` (`row_timestamp`),
FULLTEXT KEY `twitter_user_description_ft_index` (`description`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `twitter_user_follower` (
`id` bigint NOT NULL AUTO_INCREMENT,
`twitter_user_id` bigint NOT NULL,
`follower_twitter_user_id` bigint NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`follower_download_id` bigint DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `twitter_user_follower_twitter_user_id_index` (`twitter_user_id`),
KEY `twitter_user_follower_follower_download_id_index` (`follower_download_id`),
KEY `tuf_twitter_user_follower_download_key` (`twitter_user_id`,`follower_download_id`,`follower_twitter_user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=68494675 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
解释输出
+----+-------------+-----------------------+------------+----------+-------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------+---------+----------------------------------------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------------------+------------+----------+-------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------+---------+----------------------------------------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | twitter_user | NULL | fulltext | PRIMARY,twitter_user_username_index,twitter_user_ts,twitter_user_description_ft_index | twitter_user_description_ft_index | 0 | const | 1 | 100.00 | Using where; Ft_hints: no_ranking; Using temporary |
| 1 | SIMPLE | twitter_user_follower | NULL | ref | twitter_user_follower_twitter_user_id_index,twitter_user_follower_follower_download_id_index,tuf_twitter_user_follower_download_key | tuf_twitter_user_follower_download_key | 25 | const,const,si_data_db.twitter_user.id | 1 | 100.00 | Using index; Distinct |
+----+-------------+-----------------------+------------+----------+-------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------+---------+----------------------------------------+------+----------+----------------------------------------------------+
树输出:
-> Limit: 20 row(s) (cost=4.77..4.77 rows=1)
-> Table scan on <temporary> (cost=2.51..2.51 rows=1)
-> Temporary table with deduplication (cost=4.77..4.77 rows=1)
-> Limit table size: 20 unique row(s)
-> Nested loop inner join (cost=2.16 rows=1)
-> Filter: (match twitter_user.`description` against (''founder'')) (cost=1.06 rows=1)
-> Full-text index search on twitter_user using twitter_user_description_ft_index (description=''founder'') (cost=1.06 rows=1)
-> Limit: 1 row(s) (cost=1.10 rows=1)
-> Covering index lookup on twitter_user_follower using tuf_twitter_user_follower_download_key (twitter_user_id=4899565692, follower_download_id=7064, follower_twitter_user_id=twitter_user.id) (cost=1.10 rows=1)
更新:
根据 Bernd 的建议,我运行了这个仍然很慢的查询:
SELECT `follower`.`follower_twitter_user_id`
FROM (
SELECT `follower_twitter_user_id`
FROM `twitter_user_follower`
WHERE `twitter_user_id` = 4899565692
AND `follower_download_id` = 7440
) AS follower
JOIN `twitter_user` ON `follower`.`follower_twitter_user_id` = `twitter_user`.`id`
WHERE MATCH(twitter_user.description) AGAINST('+founder' IN BOOLEAN MODE)
limit 20 offset 0;
解释输出:
+----+-------------+-----------------------+------------+----------+-------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------+---------+----------------------------------------+------+----------+-----------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------------------+------------+----------+-------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------+---------+----------------------------------------+------+----------+-----------------------------------+
| 1 | SIMPLE | twitter_user | NULL | fulltext | PRIMARY,twitter_user_description_ft_index | twitter_user_description_ft_index | 0 | const | 1 | 100.00 | Using where; Ft_hints: no_ranking |
| 1 | SIMPLE | twitter_user_follower | NULL | ref | twitter_user_follower_twitter_user_id_index,twitter_user_follower_follower_download_id_index,tuf_twitter_user_follower_download_key | tuf_twitter_user_follower_download_key | 25 | const,const,si_data_db.twitter_user.id | 1 | 100.00 | Using index |
+----+-------------+-----------------------+------------+----------+-------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------+---------+----------------------------------------+------+----------+-----------------------------------+
解释分析输出:
-> Limit: 20 row(s) (cost=2.16 rows=1) (actual time=3779.933..91032.297 rows=20 loops=1)
-> Nested loop inner join (cost=2.16 rows=1) (actual time=3779.932..91032.285 rows=20 loops=1)
-> Filter: (match twitter_user.`description` against ('+founder' in boolean mode)) (cost=1.06 rows=1) (actual time=94.166..90001.280 rows=198818 loops=1)
-> Full-text index search on twitter_user using twitter_user_description_ft_index (description='+founder') (cost=1.06 rows=1) (actual time=94.163..89909.371 rows=198818 loops=1)
-> Covering index lookup on twitter_user_follower using tuf_twitter_user_follower_download_key (twitter_user_id=4899565692, follower_download_id=7440, follower_twitter_user_id=twitter_user.id) (cost=1.10 rows=1) (actual time=0.005..0.005 rows=0 loops=198818)
更新如果这有帮助,用户表是 125GB,追随者表在磁盘上是 5GB。
【问题讨论】:
-
由于您使用的是
LIMIT而没有ORDER BY,因此即使您似乎对此查询有合理的索引,使用附加条件的搜索可能需要更长的时间,我并不感到惊讶。我想知道是否将其重写为IN查询(没有JOIN)可能会有所帮助,但我只是在推测。 -
我一直在使用
JOIN和IN,但两者都很慢。我认为没有ORDER BY的LIMIT对性能更好,因为DBMS 不需要排序,只需选择它获得的前20 行。 -
我并不是要暗示添加
ORDER BY会提高性能。我只是说ORDER BY可能会使具有一种条件的查询在性能上与具有两种条件的查询更相似,所以我并不完全惊讶于您的实际情况中的数字如此不同。
标签: mysql sql performance query-optimization