无论如何,为了提高 SQL 查询的性能,以按标签匹配计数查找具有顺序的行

Posted

技术标签:

【中文标题】无论如何,为了提高 SQL 查询的性能,以按标签匹配计数查找具有顺序的行【英文标题】:Anyway to improve performance of SQL query to find rows with order by tag match count 【发布时间】:2021-06-20 13:40:25 【问题描述】:

服务器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
;

Item 表包含超过 2M 行。

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 表目前包含超过 60M 行。

经过一番努力寻找最佳查询后,我最终找到了 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 下降。

id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE it range fk_item_tag_tag_idx,tid_iid_idx tid_iid_idx 4 \N 3271866 Using where Using temporary
1 SIMPLE t eq_ref PRIMARY,id_UNIQUE PRIMARY 4 lm2.it.tag_id 1 Using index
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY ALL \N \N \N \N 2 Using temporary
1 PRIMARY it ref fk_item_tag_tag_idx,tid_iid_idx tid_iid_idx tid_iid_idx 4 t.id 105 Using index
2 DERIVED \N \N \N \N \N \N \N No tables used
3 UNION \N \N \N \N \N \N \N No tables used
4 UNION \N \N \N \N \N \N \N No tables used
5 UNION \N \N \N \N \N \N \N No tables used

无论如何,在我通过添加新条件缩小搜索范围之前,似乎无法加快执行时间。我想不出更好的方法来缩小搜索范围。

按时间列限制搜索查询(即显示去年的项目) 不允许用户一次搜索多个最受欢迎的标签 ... ?

【问题讨论】:

顺便说一句,请注意没有人使用 RIGHT JOIN count() 的结果进行排序很难改进...我尝试创建FIDDLE 但结果太好了,无法改进... ???? 您的时间不包括获取此列表:it.tag_id IN (3733, 5203, 5202, 1234);我的答案消除了这种开销。 select version(); 说什么? 10.3.27-MariaDB-log 【参考方案1】:

查询将必须读取您选择的标签的所有 item_tag 记录,因此对于更受欢迎的标签需要更长的时间;没有办法。

除非您在 item_tag 中有 item_id 值不在您需要排除的项目中,否则根本不需要加入项目。

您可能会看到使用标签表的一些改进(我假设存在,因为 fk_item_tag_tag_idx 索引);这应该将 item_tag 上的索引查找从范围更改为参考:

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

如果没有标签表,您可以使用 ad-hoc 表:

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

【讨论】:

谢谢。实际上,还有一个 TAG 表。我只是没有提到它,因为我认为这无关紧要。我用新的测量值更新了原始帖子。您的查询确实减少了执行时间,我不明白为什么。为什么使用附加表会使查询运行得更快? 如果-&gt; 38.922 -&gt; 33.931.之后的两个解释来自我的两个查询,看起来第一个的改进只是不必阅读项目表;它仍然首先从 item_tag 读取范围,而不是先读取标签,然后再读取 item_tag 和引用。尝试将 straight_join 添加到我的第一个查询 select straight_join it.item_id, count(*) ... 'SELECT straight_join' 查询在 49 分 44 秒内完成 :) vs 42 秒。这里解释一下:id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra 1|SIMPLE|t|index|PRIMARY,id_UNIQUE|id_UNIQUE|4|\N|330728|使用索引|使用临时|使用文件排序 1|SIMPLE|it|ref|fk_item_tag_tag_idx,tid_iid_idx|tid_iid_idx|tid_iid_idx|4|lm2.t.id|104|使用 where|使用索引【参考方案2】:

删除这些,它们会损害性能:

INDEX `fk_item_tag_tag_idx` (`tag_id`),
INDEX `fk_item_tag_item_idx` (`item_id`),

它们妨碍优化器意识到复合索引之一更好。

PRIMARY KEY 是什么?每个 InnoDB 都必须有一个 PK,最好明确声明 PK。如果是(tag_id, item_id)的组合,那就PK吧。 (但不要费心让 (item_id, tag_id) 独一无二。)

标签是什么样的?它们是短字符串吗?如果是这样,请不要为每个标签使用id。只需将字符串放在此表中即可。

进一步讨论:many:many 而且,我是根据标记经验发言。

另外,摆脱嵌套的SELECT。找到最常见的标签很简单:

SELECT tag, COUNT(*) AS counter
    FROM tags
    WHERE tag IN (...)
    GROUP BY tag
    ORDER BY counter DESC
    LIMIT 50;

(是的,您可以将tag 添加到ORDER BY。)

【讨论】:

我按照您的建议删除了单列索引,但在速度方面没有任何改变。我在这张表上没有 PK,但我确实覆盖了索引 tid_iid_idx(标签,项目)和 iid_tid_idx(项目,标签)。不一样吗?无论如何我会尝试创建PK。创建 PK 后是否必须删除覆盖索引?标签只是文字,有些很短,有些很长。 PK 可以是“复合的”。 PK 是唯一的并且是一个索引。 “标签可能很长”——即使有些标签是 100 个字符,我的建议仍然有效。

以上是关于无论如何,为了提高 SQL 查询的性能,以按标签匹配计数查找具有顺序的行的主要内容,如果未能解决你的问题,请参考以下文章

如何提高风数据SQL查询性能

如何提高sql查询的性能

如何提高sql查询的性能?

如何提高 SQL Azure 查询性能

如何提高查询性能?

如何提高子查询的性能或 sql 中子查询的替代方案