MySQL标记问题:如何选择标记为X,Y和Z的项目?

时间:2010-07-15 22:19:35

标签: mysql database tagging

我正在处理一个数据库,其中项目被“标记”了一定次数。

项目(100k行)

  • ID
  • 名称
  • 其他东西

标记(10k行)

  • ID
  • 名称

item2tag (1,000,000行)

  • ITEM_ID
  • TAG_ID
  • 计数

我正在寻找最快的解决方案:

选择标记为X,Y和Z的项目(其中X,Y和Z对应(可能)标签名称)?

这是我到目前为止所做的......我只是想确保以最好的方式做到这一点:

首先从名称中获取tag_ids:

SELECT tag.id WHERE name IN ("X","Y","Z");

然后我按那些tag_ids分组并使用必须过滤结果:

SELECT item2tag.*, count(tag_id)
  FROM item2tag
  WHERE tag_id=1 or tag_id=2 or tag_id=3
GROUP BY item_id
HAVING count(tag_id)=3;

然后我可以选择带有这些ID的项目。

SELECT * FROM item WHERE id IN ([results from prior query])

我在item2tag中有数百万行,索引在(item_id,tag_id)。这会是最快的解决方案吗?

3 个答案:

答案 0 :(得分:3)

您建议的方法可能是执行查询的最常用方法,但可能不是最快的方法。使用连接可以更快:

SELECT T1.item_id
FROM item2tag T1
JOIN item2tag T2 ON T1.item_id = T2.item_id
JOIN item2tag T3 ON T2.item_id = T3.item_id
WHERE T1.tag_id = 1 AND T2.tag_id = 2 AND T3.tag_id = 3

您应确保拥有以下索引:

  • 主要关键字(item_id,tag_id)
  • 索引(tag_id)。

我在几个不同的场景中对原始版本进行了性能测试。

  • 如果表格中几乎每个项目都被标记了至少一个被搜索的标签,原始查询大约需要5秒钟,而JOIN版本大约需要10秒钟 - 稍微慢一点。
  • 对于两个标签非常频繁出现并且其中一个标签很少发生的情况,原始查询大约需要0.9秒,而JOIN查询只需要0.003秒 - 这是一个相当大的性能提升。

我以前粘贴了用于进行性能测试的SQL。您可以自己运行此测试或稍微修改它并测试其他查询或不同的方案。

警告:不要在生产数据库上运行此脚本,因为它会修改item2tag表的内容。运行脚本可能需要几分钟时间,因为它会创建大量数据。

CREATE TABLE filler (
        id INT NOT NULL PRIMARY KEY AUTO_INCREMENT
) ENGINE=Memory;

DELIMITER $$

CREATE PROCEDURE prc_filler(cnt INT)
BEGIN
        DECLARE _cnt INT;
        SET _cnt = 1;
        WHILE _cnt <= cnt DO
                INSERT
                INTO    filler
                SELECT  _cnt;
                SET _cnt = _cnt + 1;
        END WHILE;
END
$$
CALL prc_filler(1000000);

CREATE TABLE item2tag (
    item_id INT NOT NULL,
    tag_id INT NOT NULL,
    count INT NOT NULL
);

INSERT INTO item2tag (item_id, tag_id, count)
SELECT  id % 150001, id % 10, 1
FROM    filler;
ALTER TABLE item2tag ADD PRIMARY KEY (item_id, tag_id);
ALTER TABLE item2tag ADD KEY (tag_id);

-- Make tag 3 occur rarely.    
UPDATE item2tag SET tag_id = 10 WHERE tag_id = 3 AND item_id > 0;

SELECT T1.item_id
FROM item2tag T1
JOIN item2tag T2 ON T1.item_id = T2.item_id
JOIN item2tag T3 ON T2.item_id = T3.item_id
WHERE T1.tag_id = 1 AND T2.tag_id = 2 AND T3.tag_id = 3;

SELECT item_id
FROM item2tag
WHERE tag_id=1 or tag_id=2 or tag_id=3
GROUP BY item_id
HAVING count(tag_id)=3;

答案 1 :(得分:0)

你最好放置一个将tag_id作为第一列的索引 - 否则使用tag_id 1查找所有项目将需要全表扫描(当然,任何tag_id都相同)。

答案 2 :(得分:0)

根据使用单个标签标记的项目数量,您可以通过获取标记有一个标记的项目列表,然后过滤它以查找其他标记,例如:

select item_id from item2tag
where item_id in (
    select item_id from item2tag
    where item_id in (
        select item_id from item2tag where tag_id = TID1
    ) and tag_id = TID2
) and tag_id = TID3