【发布时间】: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_idIN (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
标签: mysql database query-optimization database-performance database-optimization