mysql - 无论如何,要提高 SQL 查询的性能,以按标签匹配计数查找具有 order 的行

标签 mysql performance query-optimization

服务器: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 下降。

<表类=“s-表”> <标题> id 选择类型 表 类型 可能的键 键 key_len 引用 行 额外 <正文> 1 简单 它 范围 fk_item_tag_tag_idx,tid_iid_idx tid_iid_idx 4 \N 3271866 使用地点 使用临时 1 简单 t eq_ref 主要,id_UNIQUE 主要 4 lm2.it.tag_id 1 使用索引
<表类=“s-表”> <标题> id 选择类型 表 类型 可能的键 键 key_len 引用 行 额外 <正文> 1 主要 全部 \N \N \N \N 2 使用临时 1 主要 它 引用 fk_item_tag_tag_idx,tid_iid_idx tid_iid_idx tid_iid_idx 4 t.id 105 使用索引 2 衍生 \N \N \N \N \N \N \N 未使用表格 3 联盟 \N \N \N \N \N \N \N 未使用表格 4 联盟 \N \N \N \N \N \N \N 未使用表格 5 联盟 \N \N \N \N \N \N \N 未使用表格

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

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

最佳答案

查询必须读取所选标签的所有 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/

相关文章:

performance - 异步和等待 : are they bad?

mysql - 优化查询,重复具有最早日期的行

使用 Jmeter 进行性能负载测试以及 Assets 文件如何影响结果

java - 使用 Hibernate 5 和 JPA 连接到 MySQL 服务器时出错

mysql - 我的查询需要数年才能计算+分组依据

mysql - Spring JpaRepository findBy...In(Collection) 返回并集而不是交集

performance - 如何在 Dart 中轻松/动态加载类?

postgresql:具有外键的多个多列索引?

PHP/MySql - 查询具有嵌套对象的对象的最佳方式

java - 为什么 setAutoCommit(false) 在 JDBC 中不起作用?