我正在处理一个数据库,其中项目被“标记”了一定次数。
项目(100k行)
标记(10k行)
item2tag (1,000,000行)
我正在寻找最快的解决方案:
选择标记为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)。这会是最快的解决方案吗?
答案 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
您应确保拥有以下索引:
我在几个不同的场景中对原始版本进行了性能测试。
我以前粘贴了用于进行性能测试的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