Я не вижу проблемы с простым решением: таблица для элементов, таблица для тегов, перекрестная таблица для «тегирования»
Индексы на кросс-таблице должны быть достаточно оптимизационными. Выбор подходящих предметов будет
SELECT * FROM items WHERE id IN
(SELECT DISTINCT item_id FROM item_tag WHERE
tag_id = tag1 OR tag_id = tag2 OR ...)
И пометка будет
SELECT * FROM items WHERE
EXISTS (SELECT 1 FROM item_tag WHERE id = item_id AND tag_id = tag1)
AND EXISTS (SELECT 1 FROM item_tag WHERE id = item_id AND tag_id = tag2)
AND ...
, что, правда, не так эффективно для большого количества сравниваемых тегов. Если вы хотите сохранить количество тегов в памяти, вы можете начать запрос с тегов, которые встречаются не часто, поэтому последовательность AND будет оценена быстрее. В зависимости от ожидаемого количества сравниваемых тегов и ожидаемого совпадения с любым из них, это может быть хорошим решением, если вы хотите сопоставить 20 тегов и ожидать, что какой-то случайный элемент будет соответствовать 15 из них, тогда это все равно будет тяжелым в базе данных.