服务器:10.3.27-MariaDB-log
我有项目表和多对多项目标签表。在搜索表单中,我列出了想要查找的所有标签,并期望收到项目 ID 的有序列表,其中标签计数按降序排列。没有什么异常。
CREATE TABLE `item` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
...
PRIMARY KEY (`id`)
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
AUTO_INCREMENT=2283235
;
项目表包含超过 200 万行。
CREATE TABLE `item_tag` (
`item_id` INT(10) UNSIGNED NOT NULL,
`tag_id` INT(10) UNSIGNED NOT NULL,
INDEX `fk_item_tag_tag_idx` (`tag_id`),
INDEX `fk_item_tag_item_idx` (`item_id`),
INDEX `tid_iid_idx` (`tag_id`, `item_id`),
INDEX `iid_tid_idx` (`item_id`, `tag_id`)
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
;
Item_tag 表目前包含超过 6000 万行。
经过一番努力寻找最佳查询后,我最终得到了 2 个解决方案,它们在速度方面几乎相同:
SELECT
i.`id`,
counter.cnt
FROM `item` i
RIGHT JOIN (
SELECT
it.item_id,
COUNT(it.item_id) AS cnt
FROM
item_tag it
WHERE
it.tag_id IN (3733, 5203, 5202, 1234) << --- test TAG_IDs
GROUP BY
it.item_id
ORDER BY NULL
) counter ON counter.item_id = i.id
ORDER BY
counter.cnt DESC, i.id DESC
LIMIT 50;
在4,118秒内执行。
和
SELECT
it.item_id,
COUNT(*) AS cnt
FROM item_tag it
INNER JOIN item i ON i.id = it.item_id
WHERE
it.tag_id IN (3733, 5203, 5202, 1234)
GROUP BY
it.item_id
ORDER BY
cnt DESC,
it.item_id DESC
LIMIT 50;
在3,386秒内执行。
查询的执行时间很大程度上取决于指定标签的频率。之前的时间针对以下标签和计数:
| tag_id | counter (number of items) |
| -------- | ------- |
| 3733 | 457357 |
| 5203 | 14300 |
| 5202 | 13803 |
| 1234 | 0 |
但是,如果我对更流行的标签重复这些查询,我的查询执行时间会飙升至 40-50 秒:
| tag_id | counter (number of items) |
| -------- | ------- |
| 3927 | 497732 |
| 4189 | 472916 |
| 3714 | 505325 |
| 3702 | 369115 |
第一种类型查询说明:
id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra
--|------|-----|-----|-------|---|-------|--|---|------
1|PRIMARY|<derived2>|ALL|\N|\N|\N|\N|3271866|Using temporary| Using filesort
1|PRIMARY|i|eq_ref|PRIMARY|PRIMARY|4|counter.item_id|1|
2|DERIVED|it|range|fk_item_tag_tag_idx,tid_iid_idx|tid_iid_idx|4|\N|3271866|Using where| Using index| Using temporary
第二类查询说明:
id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra
--|------|-----|-----|-------|---|-------|--|---|------
1|SIMPLE|it|range|fk_item_tag_tag_idx,fk_item_tag_item_idx,tid_iid_idx,pid_kid_idx|tid_iid_idx|4|\N|3271866|Using where| Using index| Using temporary| Using filesort
1|SIMPLE|i|eq_ref|PRIMARY|PRIMARY|4|lm2.it.item_id|1|Using index
正如你可能已经猜到的:)这一次对我来说是无法接受的。我想知道可以进行什么类型的优化来减少查询执行时间?
更新2
还有另一个表“TAG”:
CREATE TABLE `tag` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`tag` VARCHAR(45) NOT NULL COLLATE 'utf8mb4_unicode_ci',
`last_assigned` TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `id_UNIQUE` (`id`),
UNIQUE INDEX `tag_UNIQUE` (`tag`)
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
AUTO_INCREMENT=372469;
我已经测试了 ysth 答案中的其他查询,执行时间从 44.563 秒 -> 38.922 -> 33.931 下降。
无论如何,在我通过添加新条件缩小搜索范围之前,似乎没有办法加快执行时间。我几乎想不出更好的方法来缩小搜索范围。
- 按时间列限制搜索查询(即显示去年的项目)
- 不允许用户同时搜索多个最受欢迎的标签
- ...?
最佳答案
查询必须读取所选标签的所有 item_tag 记录,因此对于更受欢迎的标签将花费更长的时间;没有办法解决这个问题。
除非 item_tag 中的 item_id 值不在需要排除的项目中,否则根本不需要加入项目。
您可能会看到使用标签表的一些改进(我假设存在,因为 fk_item_tag_tag_idx 索引);这应该将 item_tag 上的索引查找从 range 更改为 ref:
select it.item_id, count(*)
from tag t
join item_tag it on t.id=it.tag_id
where t.id in (3733, 5203, 5202, 1234)
group by it.item_id order by count(*) desc, it.item_id desc limit 50
如果没有标签表,您可以使用临时表:
select it.item_id, count(*)
from (select 3733 id union all select 5203 union all select 5202 union all select 1234) t
join item_tag it on t.id=it.tag_id
group by it.item_id order by count(*) desc, it.item_id desc limit 50
关于mysql - 无论如何,要提高 SQL 查询的性能,以按标签匹配计数查找具有 order 的行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68056409/