将“毒”方式正常化真的值得吗? (3NF)

Posted

技术标签:

【中文标题】将“毒”方式正常化真的值得吗? (3NF)【英文标题】:Is it really worth it to normalize the "Toxi" way? ( 3NF ) 【发布时间】:2011-01-05 01:11:30 【问题描述】:

我处于数据库设计的早期阶段,所以还没有最终结果,我正在为我的线程使用“TOXI”3 表设计,其中包含可选标签,但我不禁感到加入并不是真正必要的,也许我只需要依靠我的posts 表中的一个简单标签列,我可以在其中存储一个类似<tag>, <secondTag> 的varchar。

回顾一下:

是否值得在 2 个标记表上进行额外的左连接而不是在我的 posts 表中只包含一个标记列。 有什么方法可以优化我的查询吗?

架构

CREATE TABLE `posts` (
    `post_id` INT UNSIGNED PRIMARY AUTO_INCREMENT,
    `post_name` VARCHAR(255)
) Engine=InnoDB;

CREATE TABLE `post_tags` (
    `tag_id` INT UNSIGNED PRIMARY AUTO_INCREMENT,
    `tag_name` VARCHAR(255)
) Engine=InnoDB;

CREATE TABLE `post_tags_map` (
    `map_id` INT PRIMARY AUTO_INCREMENT,
    `post_id` INT NOT NULL,
    `tags_id` INT NOT NULL,
    FOREIGN KEY `post_id` REFERENCES `posts` (`post_id`),
    FOREIGN KEY `post_id` REFERENCES `post_tags` (`tag_id`)
) Engine=InnoDB;

样本数据

INSERT INTO `posts` (`post_id`, `post_name`)
  VALUES
(1, 'test');

INSERT INTO `post_tags` (`tag_id`, `tag_name`)
  VALUES
(1, 'mma'),
(2, 'ufc');

INSERT INTO `posts_tags_map` (`map_id`, `post_id`, `tags_id`)
  VALUES
(1, 1, 1),
(2, 1, 2);

当前查询

SELECT 
    posts.*,
    GROUP_CONCAT( post_tags.tag_name order by post_tags.tag_name ) AS tags

  FROM posts
    LEFT JOIN posts_tags_map
      ON posts_tags_map.post_id = posts.post_id
    LEFT JOIN post_tags
      ON posts_tags_map.tags_id = posts_tags.tag_id

  WHERE posts.post_id = 1
  GROUP BY post_id

结果

如果有标签:

post_id post_name 标签 1 次测试 mma、ufc

【问题讨论】:

为什么是map_id(post_id, tags_id) 这对是独一无二的。 @jensgram:有些人更喜欢代理键,即使在多对多表上也是如此。而且有些 ORM 不支持复合 PK... 【参考方案1】:

将所有标签放在不同的记录中(标准化)意味着您可以在需要时更轻松地重命名标签并跟踪标签名称历史记录。

例如SO,至少三次重命名SQL Server相关标签(mssql -> sqlserver -> sql-server)。

在一条记录中包含所有标签(非规范化)意味着您可以使用 FULLTEXT 索引索引此列,并一次搜索具有两个或多个标签的帖子:

SELECT  *
FROM    posts
WHERE   MATCH(tags) AGAINST('+mma +ufc')

这也是可能的,但归一化设计效率较低。

(不要忘记将@ft_min_word_len 调整为3 字符或更少字符的索引标签才能正常工作)

您可以结合两种设计:存储映射表和非规范化列。不过,这将需要更多的维护。

您还可以将规范化设计存储在您的数据库中,并使用您提供的查询将标签提供给SphinxLucene

这样,您可以使用mysql进行历史挖掘,使用Sphinx进行全文标签搜索,无需额外维护。

【讨论】:

请注意,如果使用默认设置,FULLTEXT 索引不会索引这两个单词:它至少需要 4 个字符。您可以更改设置,但必须这样做。【参考方案2】:

如果您使用 VARCHAR hack,您几乎不可能查询数据。编写一个准确有效地显示具有给定标签的所有帖子的查询将是地狱(让我们面对现实,这是标签系统的一个相当大的方面):准确性部分很难,因为您需要考虑所有可能性逗号;效率部分很难,因为在字符串中搜索比查看字段的完整值要慢得多(如果你可以使用整数,更是如此)。

所以是的,这是绝对值得的。

就加快查询速度而言 - 确保您的表上有相关索引。对查询运行 EXPLAIN 以查看任何瓶颈所在的位置。我不认为在处理每个帖子时获取标签会更好,但它可能是 - 我不确定 MySQL 在字符串操作方面的效率如何,这就是你执行 GROUP_CONCAT 时所做的事情.

【讨论】:

【参考方案3】:

如果您有一个带有标签列表的 varchar,那么您对标签的查询将会非常慢。您将按照 where post.tag like '%mytag%' 的行做一些事情,这不会在任何地方执行,也不会在索引上搜索钥匙。

[编辑] 这项研究向performance 展示了制作标记系统的各种方法(包括 FULLTEXT 索引),并建议您希望在何时何地使用每一种方法。

【讨论】:

FULLTEXT 索引是专门为这样的查询而设计的。 会不会像在索引列上查询 id 一样快? @Jeremy French: 哪一栏?您不能创建一个普通索引来用两个键索引一条记录。检索所有包含一系列 id 的帖子的查询当然是可能的,但效率较低。 在上述模式中查询 post_tags 的名称需要一个文本查找,全文索引将帮助您。然后在 post_tags_map 上加入一个可以使用数字索引的连接。 OP 中的替代方法是使用带有逗号分隔的标签列表的非规范化字符串。这将比规范化版本慢。 @Jeremy French:FULLTEXT 索引专为快速查询逗号分隔(或任何分隔)字符串而设计。它用尽可能多的键索引一条记录,只要字段中有不同的单词。比如说,您需要查询所有标记为sqlmysqloptimizationnormalization 的帖子(一次)。使用FULLTEXT 索引:MATCH (tags) AGAINST ('+sql +mysql +optimization +normalization') 非常简单高效。现在,您将如何对规范化架构做同样的事情?【参考方案4】:

加入(当您有正确的索引时)通常比尝试从字段中以逗号分隔的字符串的中间提取数据快得多,即使使用全文搜索也是如此。或者您可以使用一堆单独的标签字段(Tag1、tag2、tag3)并且查询仍然会更难(让我搜索 5 个字段以查找我是否使用过该标签)并且您每次都需要添加一个新列您需要添加一个新标签,并且您已经用完了现有的列。规范化的数据库设计是最好的、最高效的方法。数据库被设计为使用连接。为什么你不想使用它们超出了我的理解。

【讨论】:

以上是关于将“毒”方式正常化真的值得吗? (3NF)的主要内容,如果未能解决你的问题,请参考以下文章

多可用区 RDS 真的值得吗? [关闭]

我真的需要 MainWindow.xib 文件吗?

CSS 框架真的值得使用吗?

火的一塌糊涂的Python,真的值得你学习吗?

Kotlin真的值得学习吗?

使用 Display: None 属性,真的值得吗? [关闭]