Вопрос с тегами MySQL: как выбрать элемент, помеченный как X, Y и Z - PullRequest
3 голосов
/ 16 июля 2010

Я имею дело с базой данных, где элементы "помечены" определенное количество раз.

item (100k строк)

  • id
  • имя
  • другие вещи

тег (10 тыс. Строк)

  • id
  • имя

item2tag (1 000 000 строк)

  • item_id
  • tag_id
  • count

Я ищу самое быстрое решение для:

Выберите элементы, которые были помечены как 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;

Затем я могу просто выбрать элемент с этими идентификаторами.

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

У меня есть миллионы строк в item2tag, с индексом (item_id, tag_id).Это будет самое быстрое решение?

Ответы [ 3 ]

3 голосов
/ 16 июля 2010

Метод, который вы предложили, является, вероятно, наиболее распространенным способом выполнения запроса, но может быть не самым быстрым. Использование объединений может быть быстрее:

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;
0 голосов
/ 16 июля 2010

В зависимости от того, сколько элементов помечено отдельными тегами, вы можете сделать это, получив список элементов, помеченных одним тегом, и затем отфильтровав его на наличие других тегов, например:

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
0 голосов
/ 16 июля 2010

Вам будет лучше разместить индекс с tag_id в качестве первого столбца - в противном случае для поиска всех элементов с tag_id 1 потребуется полное сканирование таблицы (конечно же, для любого tag_id).

...