如何实现标签系统
Posted
技术标签:
【中文标题】如何实现标签系统【英文标题】:How to implement tag system 【发布时间】:2010-12-21 02:07:03 【问题描述】:我想知道实现标签系统的最佳方法是什么,就像在 SO 上使用的那样。我正在考虑这个,但我想不出一个好的可扩展解决方案。
我正在考虑有一个基本的 3 表解决方案:有一个 tags
表、一个 articles
表和一个 tag_to_articles
表。
这是解决此问题的最佳方法,还是有替代方案?使用这种方法,表格会及时变得非常大,我认为搜索效率不是很高。另一方面,快速执行查询并不那么重要。
【问题讨论】:
***.com/questions/50297616/… 【参考方案1】:相信你会发现这篇博文很有趣:Tags: Database schemas
问题:您希望有一个数据库模式,您可以在其中标记一个 书签(或博客文章或其他任何内容),带有任意数量的标签。 稍后,您希望运行查询以将书签限制为 标签的联合或交集。您还想排除(例如:减号) 搜索结果中的一些标签。
“mysqlicious”解决方案
在这个解决方案中,模式只有一个表,它是非规范化的。这种类型称为“MySQLicious 解决方案”,因为 MySQLicious 将 del.icio.us 数据导入到具有这种结构的表中。
交点 (AND) 查询“search+webservice+semweb”:
SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags LIKE "%semweb%"
联合 (OR) 查询“search|webservice|semweb”:
SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
OR tags LIKE "%webservice%"
OR tags LIKE "%semweb%"
减号 查询“search+webservice-semweb”
SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags NOT LIKE "%semweb%"
“Scuttle”解决方案
Scuttle 将其数据组织在两个表中。该表“scCategories”是“标记”表,并且有一个“书签”表的外键。
交点 (AND) 查询“书签+webservice+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.category IN ('bookmark', 'webservice', 'semweb')),然后只将所有三个标签都搜索到的书签考虑在内(HAVING COUNT(b.bId)=3)。
联合 (OR) 查询“bookmark|webservice|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
减号(排除) 查询“bookmark+webservice-semweb”,即:bookmark AND webservice AND NOT 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 COUNT 会导致查询“bookmark|webservice-semweb”。
“毒”溶液
Toxi 提出了一个三表结构。通过表“tagmap”,书签和标签是 n 到 m 相关的。每个标签可以与不同的书签一起使用,反之亦然。这个 DB-schema 也被 wordpress 使用。 查询与“scuttle”解决方案中的查询完全相同。
交点 (AND) 查询“书签+webservice+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
联合 (OR) 查询“bookmark|webservice|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
减号(排除) 查询“bookmark+webservice-semweb”,即:bookmark AND webservice AND NOT 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 COUNT 会导致查询“bookmark|webservice-semweb”。
【讨论】:
该博客文章的作者在这里。该博客不再被 Chrome 阻止(愚蠢的 wordpress 漏洞,现在移至 tumblr)。将其转换为 Markdown 的荣誉 嗨@Philipp。好的,编辑了我的答案。顺便说一句,感谢关于数据库标签系统的精彩帖子。 请注意:如果您希望 Toxi 解决方案的交叉点查询在搜索“书签”和“网络服务”时也显示书签,则需要更改“HAVING COUNT( b.id )=3" 从 3 到 "sizeof(array('bookmark', 'webservice'))"。如果您打算将其用作动态标签查询功能,则只是一个小细节。 帖子中提到的不同解决方案的性能比较链接有哪些? @kampta,不,我没有任何链接。【参考方案2】:CREATE TABLE Tags (
tag VARHAR(...) NOT NULL,
bid INT ... NOT NULL,
PRIMARY KEY(tag, bid),
INDEX(bid, tag)
)
注意事项:
这比 TOXI 更好,因为它不会通过额外的 many:many 表,这使得优化变得困难。 当然,由于冗余标签,我的方法可能稍微笨重(比 TOXI),但这只是整个数据库的一小部分,性能改进可能很显着。李> 它具有高度可扩展性。 它没有(因为它不需要)代理AUTO_INCREMENT
PK。因此,它比 Scuttle 更好。
MySQLicious 很糟糕,因为它不能使用索引(LIKE
带有 前导 通配符;子字符串的错误命中)
对于 MySQL,请务必使用 ENGINE=InnoDB 以获得“集群”效果。
相关讨论(针对 MySQL):many:many mapping table optimizationordered lists
【讨论】:
【参考方案3】:我想建议优化 MySQLicious 以获得更好的性能。 在此之前Toxi(3表)解决方案的缺点是
如果您有数百万个问题,并且每个问题有 5 个标签,那么 tagmap 表中将有 500 万个条目。因此,首先我们必须根据标签搜索过滤掉 10000 个 tagmap 条目,然后再次过滤掉这 10000 个匹配的问题。因此,如果艺术 id 是简单的数字,则过滤掉它是可以的,但如果它是一种 UUID(32 varchar),那么过滤掉需要更大的比较,尽管它已被索引。
我的解决方案:
每当创建新标签时,使用 counter++(以 10 为基数),并将该计数器转换为 base64。现在每个标签名称都将具有 base64 id。并将此 ID 与名称一起传递给 UI。 这样,您将拥有最多两个字符 ID,直到我们在系统中创建 4095 个标签。现在将这些多个标签连接到每个问题表标签列中。也添加分隔符并使其排序。
所以表格是这样的
查询时,查询 id 而不是真实的标签名称。
由于它是SORTED,标签上的and
条件将更有效(LIKE '%|a|%|c|%|f|%
)。
请注意,单个空格分隔符是不够的,我们需要双分隔符来区分sql
和mysql
等标签,因为LIKE "%sql%"
也会返回mysql
结果。应该是LIKE "%|sql|%"
我知道搜索是非索引的,但您仍然可能已在与文章相关的其他列(如作者/日期时间)上建立索引,否则将导致全表扫描。
最后使用这个解决方案,不需要内部连接,在连接条件下必须将百万条记录与 500 万条记录进行比较。
【讨论】:
团队,请提供您对此解决方案在 cmets 中的缺点的意见。 @Nick Dandoulakis 请通过提供您的 cmets 来帮助我了解上述解决方案是否可行? @Juha Syrjälä 上述解决方案好吗? 这不是很实用吗?在现实世界中,我们需要通过标签名称而不是标签 ID 进行查询?【参考方案4】:您提出的三表实现将适用于标记。
然而,堆栈溢出使用不同的实现。他们以纯文本形式将标签存储到帖子表中的 varchar 列,并使用全文索引来获取与标签匹配的帖子。例如posts.tags = "algorithm system tagging best-practices"
。我确信 Jeff 曾在某处提到过这一点,但我忘记了在哪里。
【讨论】:
这似乎超级低效。标签顺序呢?或相关标签? (例如“过程”类似于“算法”或类似的东西)【参考方案5】:如果您的数据库支持可索引数组(例如 PostgreSQL),我会推荐一个完全非规范化的解决方案 - 将标签作为字符串数组存储在同一张表上。如果没有,将对象映射到标签的辅助表是最佳解决方案。如果您需要针对标签存储额外信息,您可以使用单独的标签表,但为每个标签查找引入第二个连接是没有意义的。
【讨论】:
POstgreSQL 仅支持整数数组索引:postgresql.org/docs/current/static/intarray.html 现在它也支持文本:postgresql.org/docs/9.6/static/arrays.html【参考方案6】:您的三表解决方案没有问题。
另一种选择是限制可应用于文章的标签数量(如 SO 中的 5 个)并将其直接添加到您的文章表中。
规范化数据库有其优点和缺点,就像将事物硬连接到一个表中一样有优点和缺点。
没有什么说你不能两者兼得。重复信息与关系型数据库范式背道而驰,但如果目标是性能,您可能不得不打破这些范式。
【讨论】:
是的,将标签直接放在文章表中肯定是一种选择,尽管这种方法有一些缺点。如果您将 5 个标签存储在逗号分隔的字段中,例如 (tag1,2,3,4),这将是一种简单的方法。问题是搜索是否会更快。例如,有人想查看带有 tag1 的所有内容,您必须浏览整个文章表。这将比通过 tag_to_article 表少。但话又说回来,tags_to_article 表更苗条。还有就是每次在php里都要炸,不知道是不是需要时间。 如果你同时做(带有文章的标签,并在单独的表格中),那么这将为你提供以后为中心的搜索和以标签为中心的搜索的性能。权衡是维护重复信息的负担。此外,通过限制标签的数量,您可以将每个标签放入自己的列中。只需从文章中选择 * 在哪里 XXXXXX 并去;不需要爆炸。【参考方案7】:提出的解决方案是我能想到的解决标签和文章之间多对多关系的最佳方法——如果不是唯一可行的话。所以我的投票是“是的,它仍然是最好的”。不过,我会对任何替代品感兴趣。
【讨论】:
我同意。这些 Tag 和 TagMap 表的记录大小很小,并且在正确索引时不会显着降低性能。限制每个项目的 od 标签数量也是一个好主意。以上是关于如何实现标签系统的主要内容,如果未能解决你的问题,请参考以下文章