Вопрос

Мне было интересно, как лучше всего реализовать систему тегов, подобную той, что используется в SO.Я думал об этом, но не могу придумать хорошего масштабируемого решения.

Я подумывал о том, чтобы иметь базовое решение из 3 таблиц:имея tags таблица, представляющая собой articles таблицы и tag_to_articles таблица.

Является ли это лучшим решением этой проблемы, или есть альтернативы?Используя этот метод, таблица стала бы чрезвычайно большой по времени, и я полагаю, что для поиска это не слишком эффективно.С другой стороны, не так уж важно, чтобы запрос выполнялся быстро.

Это было полезно?

Решение

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

В Чем Проблема:Вы хотите иметь схему базы данных, в которой вы можете помечать закладку (или сообщение в блоге, или что угодно еще) таким количеством тегов, сколько захотите.Позже, вы хотите запускать запросы, чтобы ограничить закладки в объединение или пересечение теги.Вы также хотите исключить (скажем:минус) некоторые теги из результата поиска.

Решение “MySQLicious”

В этом решении схема содержит только одну таблицу, она денормализована.Этот тип называется “MySQLicious solution”, потому что MySQLicious импортирует del.icio.us данные в таблицу с такой структурой.

enter image description hereenter image description here

Пересечение (И) Запрос для “поиска + веб-сервиса + semweb”:

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags LIKE "%semweb%"

Объединение (ИЛИ) Запрос для “поиска | веб-сервиса |semweb”:

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
OR tags LIKE "%webservice%"
OR tags LIKE "%semweb%"

Минус Запрос для “поиск + веб-сервис-semweb”

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags NOT LIKE "%semweb%"

“Мусорное” решение

Скаттл организует свои данные в двух таблицах.Эта таблица “scCategories” является таблицей “тегов” и имеет внешний ключ к таблице “закладок”.

enter image description here

Пересечение (И) Запрос для “закладки + веб-сервиса + semweb”:

SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId
HAVING COUNT( b.bId )=3

Сначала выполняется поиск по всем комбинациям тегов закладок, где тегом является “bookmark“, ”webservice" или “semweb" (c.категория В ('bookmark', 'webservice', 'semweb')), затем учитываются только те закладки, для которых были найдены все три тега (ИМЕЯ COUNT(b.bId)=3).

Объединение (ИЛИ) Запрос для “закладки | веб-сервиса |semweb”: Просто опустите предложение HAVING, и у вас будет союз:

SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId

Минус (Исключение) Запрос для “закладки + веб-сервис-semweb”, то есть:закладка И веб-сервис, А НЕ semweb.

SELECT b. *
FROM scBookmarks b, scCategories c
WHERE b.bId = c.bId
AND (c.category IN ('bookmark', 'webservice'))
AND b.bId NOT
IN (SELECT b.bId FROM scBookmarks b, scCategories c WHERE b.bId = c.bId AND c.category = 'semweb')
GROUP BY b.bId
HAVING COUNT( b.bId ) =2

Исключение значения HAVING приводит к запросу “закладка | веб-сервис-semweb”.


Раствор “Токси”

Токси придумал структуру из трех таблиц.Через таблицу “tagmap” закладки и теги связаны от n до m.Каждый тег можно использовать вместе с разными закладками и наоборот.Эта схема базы данных также используется WordPress.Запросы практически те же, что и в решении “scuttle”.

enter image description here

Пересечение (И) Запрос для “закладки + веб-сервиса + semweb”

SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id
HAVING COUNT( b.id )=3

Объединение (ИЛИ) Запрос для “закладки | веб-сервиса |semweb”

SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id

Минус (Исключение) Запрос для “закладки + веб-сервис-semweb”, то есть:закладка И веб-сервис, А НЕ semweb.

SELECT b. *
FROM bookmark b, tagmap bt, tag t
WHERE b.id = bt.bookmark_id
AND bt.tag_id = t.tag_id
AND (t.name IN ('Programming', 'Algorithms'))
AND b.id NOT IN (SELECT b.id FROM bookmark b, tagmap bt, tag t WHERE b.id = bt.bookmark_id AND bt.tag_id = t.tag_id AND t.name = 'Python')
GROUP BY b.id
HAVING COUNT( b.id ) =2

Исключение значения HAVING приводит к запросу “закладка | веб-сервис-semweb”.

Другие советы

Ничего плохого в вашем решении с тремя столами.

Другой вариант - ограничить количество тегов, которые можно применить к статье (например, 5 в SO), и добавить их непосредственно в таблицу статей.

Нормализация БД имеет свои преимущества и недостатки, точно так же, как жесткие элементы в одной таблице имеют свои преимущества и недостатки.

Ничто не говорит, что вы не можете сделать оба. Повторение информации противоречит парадигмам реляционных БД, но если целью является производительность, возможно, вам придется нарушить парадигмы.

Предложенная реализация трех таблиц будет работать для тегирования.

Переполнение стека использует, однако, другую реализацию. Они хранят теги в столбце varchar в таблице сообщений в виде простого текста и используют полнотекстовое индексирование для извлечения сообщений, соответствующих тегам. Например, posts.tags = " наилучшая практика тегирования системы алгоритмов " . Я уверен, что Джефф упоминал об этом где-то, но я забыл, где.

Предлагаемое решение - лучший, если не единственный, практически осуществимый, способ, которым я могу придумать, для решения отношения «многие ко многим» между тегами и статьями. Так что мой голос за «да, он все еще лучший». Я был бы заинтересован в любых альтернативах, хотя.

Если ваша база данных поддерживает индексируемые массивы (например, PostgreSQL), я бы порекомендовал полностью денормализованное решение - хранить теги в виде массива строк в одной таблице. Если нет, лучшим решением будет сопоставление вторичной таблицы объектов с тегами. Если вам нужно хранить дополнительную информацию относительно тегов, вы можете использовать отдельную таблицу тегов, но нет смысла вводить второе объединение для каждого поиска тегов.

Я хотел бы предложить оптимизированный MySQLicious для повышения производительности.До этого недостатками раствора Toxi (3 табл.) были

Если у вас миллионы вопросов, и в каждом из них по 5 тегов, то в таблице tagmap будет 5 миллионов записей.Итак, сначала мы должны отфильтровать 10 тысяч записей tagmap на основе поиска по тегам, затем снова отфильтровать совпадающие вопросы из этих 10 тысяч.Таким образом, при фильтрации, если artical id является простым числовым, тогда все в порядке, но если это своего рода UUID (32 varchar), то для фильтрации требуется большее сравнение, хотя оно проиндексировано.

Мое решение:

Всякий раз, когда создается новый тег, используйте counter ++ (база 10) и преобразуйте этот счетчик в base64.Теперь каждое имя тега будет иметь идентификатор base64.и передайте этот идентификатор в пользовательский интерфейс вместе с именем.Таким образом, у вас будет максимум два идентификатора символа, пока в нашей системе не будет создано 4095 тегов.Теперь объедините эти несколько тегов в каждый столбец тегов таблицы вопросов.Добавьте также разделитель и сделайте его отсортированным.

Итак, таблица выглядит следующим образом

enter image description here

При выполнении запроса запрашивайте идентификатор вместо реального имени тега.Поскольку это так ОТСОРТИРОВАН, and условие в теге будет более эффективным (LIKE '%|a|%|c|%|f|%).

Обратите внимание, что одного разделителя пробела недостаточно, и нам нужен двойной разделитель, чтобы различать такие теги, как sql и mysql потому что LIKE "%sql%" вернется mysql и результаты тоже.Должно быть LIKE "%|sql|%"

Я знаю, что поиск неиндексирован, но все же вы, возможно, проиндексировали другие столбцы, связанные со статьей, такие как author / DateTime, иначе это приведет к полному сканированию таблицы.

Наконец, с этим решением не требуется внутреннее объединение, когда миллионы записей должны сравниваться с 5 миллионами записей при условии объединения.

CREATE TABLE Tags (
    tag VARHAR(...) NOT NULL,
    bid INT ... NOT NULL,
    PRIMARY KEY(tag, bid),
    INDEX(bid, tag)
)

Примечания:

  • Это лучше, чем TOXI, в том смысле, что оно не проходит через лишнюю таблицу many: many, что затрудняет оптимизацию.
  • Конечно, мой подход может быть немного более громоздким (чем TOXI) из-за избыточных тегов, но это небольшой процент от весь база данных, и улучшения производительности могут быть значительными.
  • Он обладает высокой масштабируемостью.
  • У него нет (потому что он не нуждается) суррогата AUTO_INCREMENT ПК.Следовательно, это лучше, чем Удирать.
  • MySQLicious отстой, потому что он не может использовать индекс (LIKE с ведущий джокер - карта;ложные попадания в подстроки)
  • Для MySQL обязательно используйте ENGINE=InnoDB, чтобы получить эффекты "кластеризации".

Связанные обсуждения (для MySQL):
многие: оптимизация таблицы сопоставления многих
упорядоченные списки

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top