将标签存储在数据库中的最佳实践?

时间:2010-08-18 01:08:02

标签: mysql performance optimization tags structure

我开发了一个使用标签(关键词)的网站,以便对照片进行分类。 现在,我在MySQL数据库中拥有的是一个具有以下结构的表:

image_id (int)
tag      (varchar(32))

每当有人标记图像时(如果标记有效且投票数足够),则会将其添加到数据库中。我认为这不是最佳的做事方式,因为现在我有5000个带标签的图像,标签表有超过40000个条目。我担心这会影响性能(如果它还没有影响它)。

我认为这个其他结构认为获取与特定图像相关联的标签会更快但是当我想要获取所有标签或者最常用的标签时它看起来很可怕:

image_id (int)
tags     (text) //comma delimited list of tags for the image

是否有正确的方法可以做到这两种方式或两种方式相同? 想法?

4 个答案:

答案 0 :(得分:10)

使用多对多表格将TAG记录与IMAGE记录相关联:

IMAGE

DROP TABLE IF EXISTS `example`.`image`;
CREATE TABLE  `example`.`image` (
  `image_id` int(10) unsigned NOT NULL auto_increment,
  PRIMARY KEY  (`image_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

TAG

DROP TABLE IF EXISTS `example`.`tag`;
CREATE TABLE  `example`.`tag` (
 `tag_id` int(10) unsigned NOT NULL auto_increment,
 `description` varchar(45) NOT NULL default '',
 PRIMARY KEY  (`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

IMAGE_TAG_MAP

DROP TABLE IF EXISTS `example`.`image_tag_map`;
CREATE TABLE  `example`.`image_tag_map` (
 `image_id` int(10) unsigned NOT NULL default '0',
 `tag_id` int(10) unsigned NOT NULL default '0',
 PRIMARY KEY  (`image_id`,`tag_id`),
 KEY `tag_fk` (`tag_id`),
 CONSTRAINT `image_fk` FOREIGN KEY (`image_id`) REFERENCES `image` (`image_id`),
 CONSTRAINT `tag_fk` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

答案 1 :(得分:2)

您可以制作tags表格idtag,其tag上有唯一约束,然后photo_tags表格tag_id 1}}和photo_id。仅当标记尚不存在时才将标记插入tags表。

然后,当您查询有多少张照片被某个标签标记的查询时,您将通过pk查询而不是varchar文本比较。

答案 2 :(得分:2)

在多标记搜索查询中,您必须点击所请求的每个标记。因此,图片标记集 I 必须是请求标记集 U 的超集。

I >= U

在SQL中实现这种复杂的比较是一个挑战,因为每个图像都必须单独进行限定。鉴于标签是每个图像的唯一设置:

SELECT i.* FROM images AS i WHERE {n} = (
  SELECT COUNT(*) 
  FROM image_tags AS t 
  WHERE t.image_id = i.image_id
    AND t.tag IN ({tag1}, {tag2}, ... {tagn})
)

架构:

CREATE TABLE images (
  image_id varchar NOT NULL,
  PRIMARY KEY (image_id)
)

CREATE TABLE image_tags (
  image_id varchar NOT NULL,
  tag varchar NOT NULL,
  PRIMARY KEY (image_id, tag)
)

答案 3 :(得分:0)

我相信这里没有正确或错误的答案,这完全取决于使用/数据的类型,以及您是否提供编辑标签标题或在标签上添加其他属性的能力。

顺便说一句,有一个混合解决方案,它很像标签数组,但不是将它们保存为文本而是保存标签的 id。获取带有标签的列表需要 2 次查询,但可以让您更轻松地管理标签。

让我们分解一下,然后将这 3 种方法用于 2 个主要用例。 假设有 N 个图像,M 个标签,每个标签的 TN 平均图像数和每个图像的 TM 平均标签数 (例如,我们有 5K 张图片 (N=5K)、50 个标签 (M=50)、每张图片 avg 上的 5 个标签 (TM = 5) 和每个标签 20 张图片 (TN = 20)):

  1. 在图像表上保存标签列表
    • 获取带有标签的图像列表(没有按标签过滤)是 O(N) = ~5000
    • 按特定标签过滤图像列表,是 O(N * TM) = ~25000 (5000 * 5)
  2. 将标签分离到不同的表中(使用连接表)
    • 获取带有标签的图像列表(没有按标签过滤)是 O(N * log(N * TM) * log(M)) = ~37000 (5000 * log(25000) * log(50))
    • 通过某些标签过滤图像列表,是 O(log(M) * log(N) * log(N * TM) * TN) = ~552 (log(50) * log(5000) * log( 25000) * 20)
  3. 如果您采用混合解决方案
    • 获取带有标签的图像列表(没有按标签过滤)是 s O(N + M * log(M)),在 M 很小的情况下有一个潜在的改进,你总是可以得到所有的标签什么使它成为 O(N + M)。(但请记住,这有 2 次往返 DB 的缺点)。 = ~5084 (5000 + 50 * log(50)) / ~5050
    • 按特定标签过滤图像列表是 O(log(M) + N * TM)。(但请记住,这有 2 次往返 DB 的缺点)= ~25001 (log(50) + 5 * 5000)

* 当然,仅仅将数字放在复杂性公式中是对其工作方式的过度简化,但应该给人一种大致的感觉。 * 一些数据库有比数组更复杂的类型,使用它们可能会得到更好的结果(如 postgres 中的 hstore)。