当前位置: 首页 > 科技观察

MySQL枚举类型的“八宗罪”

时间:2023-03-13 19:13:38 科技观察

MySQL的枚举(ENUM)类型是程序员们讨论的热门话题。乍一看,我们可以通过枚举类型很好地把记录值限制在允许的范围内。一个典型的例子是一个数据表,其中有一个名为“大陆板块”的字段:每个国家都位于一个大陆板块上,而且这些大陆板块不太可能经常发生变化。当然,也许有一天北美板块会和亚洲板块相撞,形成北美和亚洲,但即使你的数据库能一直用到那个时候,至少你不用去研究如何重建你的数据表,那将是当时开发人员想要完成的工作。离家更近。如果使用ENUM是唯一可以代表某个国家属于哪个大陆板块的选择,那么我们就可以进入下一步争论NoSQL的优缺点,Git和SVN的优缺点,以及哪些框架做你喜欢?这些其他问题的缺点。但是这里有一个实现枚举的一般最佳实践:维基百科这样描述关系表:...这是一个将枚举可知数据分开的表。例如,在关系型数据库的仓库数据中,仓库中的“物品”可能会有一个“状态”字段来记录申报价值,例如:“已售出、已预定、已售罄”。在极简数据库设计中,这些值存储在单独的关系表“状态”中,以满足数据库规范化。所以关系表也可以满足枚举的实现。我们来看看ENUM的“八宗罪”是什么:1.数据虐待男、女;先生夫人女士。;Africa,Asia,etc.这些人们用来作为ENUM类型字段的短词被称为数据。当你使用ENUM类型的字段时,从技术上讲,你将数据提取出来(当对应到实际的数据表时),并将其放在一个独立的位置(一种数据库的元数据,具有精确定义的字段)。这和我们平时做的限制数据类型不同:数值型字段只能存储整型数据,或者日期型字段不能为空——这些都可以,也很重要。在使用ENUM类型字段的时候,我们实际上是保存了一些数据作为这个数据模型的一个特征信息。简而言之,ENUM类型字段打破了范式要求。这看似很“学术”或“迂腐”,却是下文各种“罪状”的根源。2.更改ENUM类型字段非常昂贵。不变的是,每次创建ENUM类型的字段时,你都会说:“Thisfieldcannotbechanged”。人类普遍缺乏顾全大局的能力,预测更差,比如研发部门的新产品线,你公司的新出货计划,北美板块撞亚洲板块。使用ALTERTABLE修改整个数据表的ENUM类型字段是非常耗费资源的。如果将ENUM('red','blue','black')改为ENUM('red','blue','white'),MySQL需要重构整个数据表并检索所有数据来检查'black'这是一个无效值。MySQL真的很笨,每次你添加一个新的ENUM值它都会这样做!(传闻以后会处理ENUM类型字段的效率,但我深深怀疑它的重要性。)全表重建在小数据表中可能没有那么痛苦,但在海量数据的情况下,资源可能被封锁。关了很久很久。如果使用关系表而不是ENUM类型的字段,更改枚举集合只是使用INSERT、UPDATE和DELETE,相比之下真的很有趣。需要注意的是,当更改ENUM类型字段的枚举集时,MySQL会将新枚举集中不存在的任何现有记录转换为''(空字符串)。使用关系表,在更改和删除枚举集合时有很大的灵活性(如下所述)。3.几乎不可能为关联数据添加额外的属性。到目前为止,还没有办法以更明智的方式更改ENUM类型字段,这是我们的常态。在我们的“国家、大洲”示例中,如果我们更改“区域”会发生什么?我们没想到这个属性,但顺其自然。使用关系表设计,我们可以方便地扩展“大陆板块”数据表,并通过多种方式向其中添加我们想要的数据和字段。枚举?别说话了。另一个奇妙的灵活性体现在扩展关系表的便利性上。一个简单的标志位字段就可以指示这个“枚举值”是否可用。所以,当你的公司不再打算销售黑色装饰时,你只需要在“黑色”对应的_isdiscontinued字段中标注即可。而你仍然可以查询到已售出的颜色(译者:指的是,ENUM的修改会导致原来的,但现在没有值变成空字符串,数据失去一些特征),而你的黑色装饰品的订单仍然可以算在内!ENUM,你想试试吗?4.获取ENUM所有可能的值很麻烦。一个很常见的需求是将数据库中的数据以一个可拖动的列表的形式显示出来,例如:选择颜色:红蓝黑。如果这些值存储在一个名为'colors'的数据表中,你所要做的就是:SELECT*FROMcolors,这样数据就可以动态地显示在一个可拖动的列表中。您可以在颜色关系表中添加或更改颜色,您的炫酷订单的颜色选项会自动更新,非常棒。(译文:这里的例子应该等同于:“通过后台管理,可以为前端用户限制某类数据的选项。”这样的功能。)回到ENUM:如何获取所有枚举值?当然也可以使用ENUMvaluewithDISTINCT来查询(译:查询不同ENUM值的数据,相当于利用DISTINCT的唯一性来查询ENUM),但是这样只会返回已经使用过且存在的数据数据表中的ENUMENUM值字段可选值,不是所有可能的值。你也可以查询INFORMATION_SCHEMA,通过代码解析返回的数据,找到你想要的ENUM的所有值,但这完全是多余的。事实上,我还没有找到任何优雅和原生的SQL方式来获取所有ENUM类型字段的值。5.ENUM类型字段提供的优化有限。通常使用ENUM的正当理由无非是“优化”这个词,例如性能改进、简化模型和高可读性。然后让我们看看性能。你可以在未优化的数据库中做很多疯狂和夸张的事情,但大多数情况下,在数据达到一定规模之前不会对性能产生影响,而通常我们的产品远达不到那个规模。需要注意的一点是,由于数据库开发人员热衷于让自己的设计达到一个完整的范式,只有在遇到性能问题时才会考虑反范式。如果担心使用关系表导致速度变慢,可以在同一个benchmark下测试不同方法的性能,再考虑。不要先入为主地认为关系查询会是瓶颈,有时候可能并非如此。(请参阅支持ENUM并不总是比替代方案快得多的证据。)关于ENUM优化的另一个说法是ENUM可以有效地减少数据库中数据表的外键。不可否认,使用外键相当于用线连接了很多不同的盒子,在大型系统中,范式设计已经可以降低对人的理解和复杂查询的要求。但是我们为什么要设计模型,为什么要把模型抽象出来,让我们能够理解。尝试做一个新的数据模型图或者ER图,忽略一些小细节和关系表。有时使用ENUM看起来很简单,但实际上你需要在脑海中想到一个隐式关系表,所以并不像看起来那么简单。6.ENUM值不能在其他数据表中直接复用。当你(在数据表中)创建一个带值的ENUM字段时,这个ENUM不能直接在其他数据表中重复使用。并且当有关系表时,可以在同一个申请表下的其他多张数据表中复用。当关系表中的一个数据发生变化时,其他多个数据表也会响应。ENUM类型字段的分离将允许您在多个数据表中重复使用相同的ENUM值(需要一致性)。7.ENUM类型字段有明显的缺陷。假设你设置了一个字段"color"ENUM('blue','black','red'),然后你想INSERT一行数据,但是"color"字段是'purple',MySQL会转换无效值到''(空字符串)。处理没问题,但是如果我们使用带有外键的关系表,那么我们的数据会因为健壮性而更加可靠。同样,MySQL将枚举索引与ENUM值关联起来,在使用中会错误地调用索引而不是ENUM值,反之亦然。想象一下:CREATETABLEtest(foobarENUM('0','1','2'));mysql>INSERTINTTOtestVALUES('1'),(1);QueryOK,2rowsaffected(0.00sec)Records:2Duplicates:0Warnings:0mysql>SELECT*FROMtest;+--------+|foobar|+--------+|1||0|+--------+2rowsinset(0.00sec)我们插入了'1'(字符串),并意外插入1(无引号,数字)。MySQL会把我们的数值型数据当作一个枚举索引(不是错误,而是混淆),根据索引,ENUM字段的第一个值为0。(译注:枚举索引从1开始)8.ENUM的可移植性不高好的。ENUM类型不是SQL标准,属于MySQL,其他DBMS可能没有原生支持。PostgreSQL、MariaDB、Drizzle(后两者是MySQL的分支),我只知道这三个支持ENUM。如果有人打算迁移数据库,那么他会花更多的步骤来处理你“微妙”的ENUM字段,我相信他会“更爱你”。如果(那个人)是你,你就会发现自己真的“够聪明”。一般来说,数据库迁移不会经常发生,而且由于每个人都认为迁移数据库一定会出错,所以这是“第八大罪”。什么时候适合使用ENUM:1.当你需要存储准确和恒定的值时,大陆板块就是最好的例子,定义非常精确。另一个常见的例子是头衔:先生、夫人、小姐,或扑克花色:方块、梅花、红心、黑桃。然而,即使有这些例子,也有需要扩大取值范围的时候(比如当有人需要你称呼“Dr.Chen”而不是“Mr.Chen”,或者当你需要用一个扑克游戏中的小丑牌)。2.你永远不需要存储额外的关联信息来使用扑克牌的例子。老少皆宜的扑克游戏,其规则是梅花和黑桃为黑色,方块和红心为红色(例如,euchre)。如果我们需要将其他信息与西装相关联怎么办,例如颜色?如果我们使用关系表,那么我们只需要在关系表中添加新的字段,这是一件小事。如果我们用ENUM来表示花色,我们就很难准确的表示出花色与颜色之间的关联,所以只能在应用层实现这种关联。3.ENUM值的个数大于2小于20,如果你只有两个ENUM值,完全可以用更高效的TINYINT(1)或更高效的BIT(1)(MySQL5.0.3及以上)。例如:genderENUM('male','female')可以转化为:is_maleBIT(1)。当你只有两个选项时,可以使用布尔值true/false,结合字段名中的“is”关键字来区分。至于20的上限,是的,ENUM实际上最多可以容纳65535个值,但请不要尝试。超过二十个值会变得很累赘,超过五十个势必难以管理和使用。如果无论如何都必须使用ENUM:1.出于某种原因,ENUM值不应使用定义为字符数据的数字ENUM。使用数字列类型来存储数字并不是说你是错误的,但是有充分的证据表明MySQL在内部使用数字来引用索引(参见上面的#7)。无论如何不要在ENUM中存储数字,好吗?2.考虑使用严格模式开启严格模式,至少插入不存在的ENUM值会报错。否则,只会发出警告并将值设置为空字符串“”(枚举索引0)。复制注意:如果你设置了IGNORE,错误仍然会被忽略。结论从开发和维护的角度做有意义的事情,然后在出现性能问题时再考虑优化——一般来说,是用关系表还是用ENUM类型是有争议的。性能瓶颈(这个概念)被滥用已经是不争的事实。开发人员浪费大量时间思考、担心(例如)非关键代码的速度。这些对效率的高要求给调试和维护造成了很大的负面影响。我们应该忽略那一小部分效率,就(达到)97%(效率)而言,过早优化是万恶之源。