【问题标题】:MySQL: Enum vs Varchar-with-IndexMySQL:枚举与带索引的 Varchar
【发布时间】:2018-08-17 17:55:17
【问题描述】:

假设,我需要创建一个表,其中一列将具有来自这个有限且永不更改的集合的值: 'all'、'local'、'qa'、'staging' 和 'production'。

在这种情况下使用enum 数据类型看起来是一个合适的解决方案,但是在阅读了this article 和互联网上的其他一些帖子之后,我觉得不鼓励使用它。所以,如果我想要创建一个查找表并且保持evnname的组合唯一也是一个要求,那么我最好的选择是什么具有 ENUM 类型的列和具有 VARCHAR 类型但在其上创建了索引的列。

同时考虑到此表中的插入很少见,我们希望此特定查询执行得更快:

SELECT `enabled` FROM `features`
WHERE `name` = 'some_featuere'
AND `env` IN('all', 'qa')
ORDER BY `enabled` ASC limit 1;

其中哪一个是更好的设计,为什么?

CREATE TABLE `features` (
  `id` INTEGER  NOT NULL AUTO_INCREMENT,
  `name` VARCHAR (50) NOT NULL,
  `env` ENUM('all', 'local', 'qa', 'staging', 'production') NOT NULL,
  `enabled` TINYINT(1) DEFAULT 0,
  `created_at` DATETIME,
  `updated_at` DATETIME,

  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_unq_features_name_env` (`name`,`env`)
);

CREATE TABLE `features` (
  `id` INTEGER  NOT NULL AUTO_INCREMENT,
  `name` VARCHAR (50) NOT NULL,
  `env` VARCHAR(10) NOT NULL,
  `enabled` TINYINT(1) DEFAULT 0,
  `created_at` DATETIME,
  `updated_at` DATETIME,

  PRIMARY KEY (`id`),
  INDEX `idx_features_env` (`env`),
  UNIQUE KEY `idx_unq_features_name_env` (`name`,`env`)
);

【问题讨论】:

  • 您不想要查找表,这很糟糕。使用整数。你有 5 个选项。不要过度设计这个。使用查找表,不要重新发明轮子。

标签: mysql database indexing database-design sqldatatypes


【解决方案1】:

对您的问题的简短回答是“两者都不是”,因为您的查询在这两种情况下都会使用名称/env 上的索引。但是,如果我必须解决一个问题,我会选择 VARCHAR 而不是 ENUM,因为它是两个弊端中较小的一个,但我认为您的方法可能存在其他一些问题。

首先,VARCHAR 选项只会重复该文章中提到的 ENUM 问题(即添加属性或相关数据),而可能失去您可能从 ENUM 获得的唯一优势,即数据完整性。您可以通过查找获得数据完整性,而无需 ENUM 的邪恶。

其次,您可能关注的是查询不存在的性能问题。它多久运行一次?它有多慢?就目前而言,您在NAME/ENV 上有一个索引,我想不出加快查询速度的唯一方法是包含ENABLED 的覆盖索引,但我怀疑它是性能杀手,因为它是,我怀疑你会看到加入查找表的差别很小。

第三,“ALL”作为一个选项几乎没有意义,除非一项功能一次只能部署在一个环境中或同时部署在所有环境中。如果这不成立,那么每当您想应用“ALL”选项时,您就必须删除与功能名称相关的所有其他记录。 “ALL”还可以防止在不同环境中选择性地启用/禁用功能或单独记录创建/更新事件。这引入了不需要存在的数据管理问题。

第四,IDNAMECREATED_ATUPDATED_AT 列都是与功能直接相关的属性。 ENVENABLED 列与该功能的部署位置和方式相关。乍一看,这表明将这些数据存储在一个完全独立的表中(可能使用CREATED_ATUPDATED_AT 来指示它们首次部署和最后更新的时间)。我个人会使用 Feature、Environment 和 Feature_Environment 作为单独的表,外键从 Feature_Environment 到另外两个。

【讨论】:

  • 感谢您的洞察保罗。这将是一个频繁的查询。例如,如果有一个在登录时发送通知的功能,那么查询将运行并且一个条件将检查是否在每个用户的每次登录时都启用了该功能。这只是一个用例,还有更多。 'all' 是为了保持行数最少,如果仅在说 'prod' 时禁用功能,那么我们只需要两行。从设计的角度来看,拥有 3 个表的想法很好,但这也意味着将 3 个表连接到一个应用程序执行次数最多的查询中。跟踪何时启用功能并不重要。
  • 在 Rick James 的回答之后进一步思考 all 的价值。我认为all 是不必要的,应该放在一边。查询速度更快的优势在于记录少或插入/更新方便
  • 我对 ALL 的问题更多地与冗余和歧义有关。使用 ALL,您最终可能会得到多个记录相同事实的记录,例如一条记录表明在 QA 中启用了功能 x,另一条记录在 ALL 中启用了功能 x - 这些记录了相同的事实。同时它也允许相互矛盾的事实,例如功能 x 在 ALL 中被禁用,功能 x 在 QA 中启用。
  • 当然还有另一种看待这个的方式。您不会构建功能然后不部署它们或启用它们。在某些情况下,您不希望在特定环境中启用该功能,但这通常是例外。您可以假设某个功能在环境中已启用,除非有记录将其记录为已禁用。 disabled_features 表甚至不需要启用/禁用标志,记录的存在会告诉您您需要的一切。
【解决方案2】:

亲 ENUM 和反 ENUM 派别之间正在进行一场“宗教”战争。您已阅读其中一篇“反”文章。但是那篇文章中的许多“邪恶”可能不适用于您的情况。

如果当前的 PK id 被删除并将其替换为

,可以加快您的查询速度
PRIMARY KEY(name, env)

之后,就不需要二级索引了。

您需要在辅助键中查找,然后进入 PK 以获取第三列。之后,排序并交付一行。

更改 PK 可避免额外查找。而且这种变化不应该有“不利因素”。

如果表中有数百万行,并且每个env 要求的值可能有数千个候选者,那么这会更快,因为它不会收集“数千”行,对它们进行排序,只送一份。相反,它得到两行并从中挑选:

    (   SELECT  `enabled`
            FROM  `features`
            WHERE  `name` = 'some_featuere'
              AND  `env` = 'all'
            ORDER BY  `enabled` ASC
            limit  1
    )
    UNION DISTINCT
    (  SELECT  `enabled`
            FROM  `features`
            WHERE  `name` = 'some_featuere'
              AND  `env` = 'qa'
            ORDER BY  `enabled` ASC
            limit  1 
    )
    ORDER BY  `enabled`
    LIMIT  1;

是的,ORDER BYLIMIT 重复了。对于小型数据集,我不推荐 OR --> UNION,因为有很多步骤,每个步骤都有开销。

无论您使用 ENUM 还是其他方法,我的答案都适用。它确实假设 InnoDB。

【讨论】:

    【解决方案3】:

    这是一个不同的答案 - 使用 SET 数据类型。

    以下是表格更改:

    `env` SET('local', 'qa', 'staging', 'production') NOT NULL,
    PRIMARY KEY (`name`)
    

    没有辅助键,没有'all',每个name只有一行。

    但是,测试变得更加混乱。还是变简单了??那是

    AND `env` IN('all', 'qa')
    

    -->

    AND env = 'qa'
    

    因为现在测试只针对一件事。凌乱的all 不见了。

    如果一切都符合业务逻辑,那么表和查询就更简单、更快了。

    如果我在业务逻辑上错了,请详细说明。可能有办法挽救这个答案(使用SET 而不是IN)。

    【讨论】:

    • 我同意将all 放在一边。带来all 的原因是为了保持行数最少,方便插入/更新记录。但既然我们知道这张表上的插入和更新是很少见的,而且查询这张表的频率很高,所以我们不必担心行数或插入新记录是否方便。
    • @shehzadali - SET 方法不会减少行数吗? (每个name 一行。)
    猜你喜欢
    • 1970-01-01
    • 2011-09-05
    • 1970-01-01
    • 1970-01-01
    • 2021-04-18
    • 1970-01-01
    • 2018-02-08
    • 2013-01-25
    • 1970-01-01
    相关资源
    最近更新 更多