MySQL 性能:两个表在公共外键上的连接:仍然“使用 where”
Posted
技术标签:
【中文标题】MySQL 性能:两个表在公共外键上的连接:仍然“使用 where”【英文标题】:MySQL performance: join of two tables on common foreign key: still "using where" 【发布时间】:2015-11-24 11:37:34 【问题描述】:我有以下表格:test_user
、test_tag
、product
、productinfo
(productinfo
有一个指向 product
的外键。
此外,我还有一些表格,这些表格模拟了之前表格之间的一些关系:test_usertagging
(FK 到 test_tag
和 test_user
)、productinfo_tags
(FK 到 test_tag
和 test_productinfo
)。
(下面我附上更完整的描述,但这是主要思想)。
现在我有一个产品 ID 和用户 ID 的列表,我想找到它们之间的共同标签,以及来自 test_usertagging
的附加 extra_info
列:
mysql> explain SELECT `test_usertagging`.`user_id`, `test_usertagging`.`tag_id`, `test_usertagging`.`extra_info`
FROM `productinfo`
INNER JOIN `productinfo_tags` ON ( `productinfo_tags`.`productinfo_id` = `productinfo`.`id` )
INNER JOIN `test_tag` ON ( `test_tag`.`id` = `productinfo_tags`.`tag_id` )
INNER JOIN `test_usertagging` ON ( `test_usertagging`.`tag_id` = `test_tag`.`id` )
WHERE
(`test_usertagging`.`user_id` IN (1,2,3,4 ... ) AND
`productinfo`.`product_id` IN ('abc', 'def', '000', '111', ...));
格式化查询:
SELECT test_usertagging.user_id,
test_usertagging.tag_id,
test_usertagging.extra_info
FROM productinfo
JOIN productinfo_tags ON productinfo_tags.productinfo_id = productinfo.id
JOIN test_tag ON test_tag.id = productinfo_tags.tag_id
JOIN test_usertagging ON test_usertagging.tag_id = test_tag.id
WHERE test_usertagging.user_id IN (1,2,3,4 ... )
AND productinfo.product_id IN ('abc', 'def', '000', '111', ...)
我得到的是:
+----+-------------+------------------+--------+------------------------------------------------------------------+------------------------------+---------+--------------------------------------+------+--- -----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------------+--------+------------------------------------------------------------------+------------------------------+---------+--------------------------------------+------+--- -----------------------+
| 1 | SIMPLE | productinfo | range | PRIMARY,productinfo_218f3960 | productinfo_218f3960 | 62 | NULL | 55 | Using where; Using index |
| 1 | SIMPLE | productinfo_tags | ref | productinfo_id,productinfo_tags_4b5946a2,productinfo_tags_5659cca2 | productinfo_id | 4 | tookyo_prod.productinfo.id | 1 | Using index |
| 1 | SIMPLE | test_tag | eq_ref | PRIMARY | PRIMARY | 4 | tookyo_prod.productinfo_tags.tag_id | 1 | Using index |
| 1 | SIMPLE | test_usertagging | ref | test_usertagging_5659cca2 | test_usertagging_5659cca2 | 4 | tookyo_prod.productinfo_tags.tag_id | 217 | Using where |
+----+-------------+---------------------+--------+------------------------------------------------------------------+------------------------------+---------+--------------------------------------+------+--------------------------+
这个查询执行得很糟糕。困扰我的是最后一行(test_usertagging)的“使用位置”(无索引) - 正确的索引列在key
下,但它仍然显示“使用位置”。
我尝试添加FORCE INDEX
,但这并没有改善问题(因为无论如何都列出了正确的索引)。
使用STRAIGHT JOIN
只会更改最后两行之间的顺序。 (请注意,test_tag
表本身对于此查询是多余的;完全删除它不会改变任何东西)。
知道如何从test_usertagging
使用相关可用索引(user_id
或tag_id
)中的一个或两个进行查询吗?
这是连接表的SHOW CREATE TABLE
的输出:
CREATE TABLE `test_usertagging` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tag_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `usertagging_user_per_tag_is_unique` (`user_id`,`tag_id`),
KEY `test_usertagging_5659cca2` (`tag_id`),
CONSTRAINT `test_usertagging_ibfk_3` FOREIGN KEY (`tag_id`) REFERENCES `test_tag` (`id`),
CONSTRAINT `test_usertagging_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `test_user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6456921 DEFAULT CHARSET=utf8
CREATE TABLE `productinfo` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_id` varchar(20) NOT NULL,
`text` varchar(256) NOT NULL,
`inftype` varchar(64) NOT NULL,
`extra_info` varchar(256) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `productinfo_218f3960` (`product_id`),
CONSTRAINT `product_id_refs_rpk_d9184c73` FOREIGN KEY (`product_id`) REFERENCES `product` (`rpk`)
) ENGINE=InnoDB AUTO_INCREMENT=44088 DEFAULT CHARSET=utf8
CREATE TABLE `productinfo_tags` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`productinfo_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `productinfo_id` (`productinfo_id`,`tag_id`),
KEY `productinfo_tags_4b5946a2` (`productinfo_id`),
KEY `productinfo_tags_5659cca2` (`tag_id`),
CONSTRAINT `productinfo_id_refs_id_1c393f74` FOREIGN KEY (`productinfo_id`) REFERENCES `productinfo` (`id`),
CONSTRAINT `tag_id_refs_id_0afa07dc` FOREIGN KEY (`tag_id`) REFERENCES `test_tag` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=62637 DEFAULT CHARSET=utf8
【问题讨论】:
您在 SHOW CREATE TABLE 中又错过了一张表。由于test_usertagging
.user_id
IN (1,2,3,4 ... ),您正在“使用 where”。 MySQL加入表,然后检查这个条件是否满足
如果您将WHERE test_usertagging.user_id IN (1,2,3,4 ... )
改写为WHERE test_usertagging.user_id BETWEEN 1 AND 19
或类似的东西,您将使该搜索词可搜索。此外,您的 EXPLAIN 输出中的行数并不多。每个表有多少行?你有什么表现?您需要什么性能?
@SamIvichuk 但是它不能使用 user_id 上的索引来检查更少的行吗?
@OllieJones 不幸的是,实际上这些是任意数字(用户 ID),而不是连续的。这些表并不小——它们每个都有 100,000 到数百万行。这里给出的行数是 MySQL 对匹配行的估计。查询需要几分钟(例如 3 分钟)。我希望正确的实现可以在几秒钟内完成,我想 20 秒是可以接受的。
以及显示丢失的表格@OllieJones
【参考方案1】:
在典型的多对多映射表中,例如test_usertagging
和productinfo_tags
,通常不需要id AUTO_INCREMENT PRIMARY KEY
。去掉该列,将复合UNIQUE
键提升为PRIMARY KEY
。
这种变化可能会加快一个方向,因为通过 PK 进行查找的速度大约是通过辅助键进行查找的两倍。
您的标题谈到了FOREIGN KEY
,而实际上,重要的是索引。 (如果 FK 尚不存在,则 FK 将创建一个索引。)
【讨论】:
以上是关于MySQL 性能:两个表在公共外键上的连接:仍然“使用 where”的主要内容,如果未能解决你的问题,请参考以下文章