背景

在某个 Go 后端项目中,一些枚举字段在程序中使用了 int8 类型定义。数据库中存储使用 tinyint。

但是随着时间的推移,读代码时通过常量名还能很容易的理解其含义。

但是查看数据库时,想要了解这些枚举值数字的含义,每次都需要去查阅代码中的常量定义。仔细分析,需要进行如下脑力消耗和操作:

  • 努力回忆这个数字的含义,一般需要消耗 3 ~ 5 秒时间,发现记不起来:
  • 打开 ID,打开项目,等待 IDE 启动,一般要消耗 10 秒左右时间
  • 一层一层的从众多代码文件中查找枚举的定义位置,一般要消耗 10 秒左右时间
  • 一个一个数 iota 数到枚举值所定义的常量名,一般要消耗 1~5 秒钟时间

这一个过程,浪费的毫无意义的时间暂且不提,对精力的消耗以及打断思路是非常让人恼火的。

除此之外,为了沟通方便,后端一般还要将数字类型转换为字符串类型返回给前端,这又带来了很多没有必要的转换相关的代码的开发和维护。

因此,我就萌生了,直接在项目的所有地方都使用字符串枚举类型的想法(主要在数据库)。

入行以来一直以来枚举使用数字类型似乎是一种无需辩驳的 “常识”,这个 “常识” 是对的吗? 反过来想,“常识” 既然形成了一定是有其原因的,如果不加论证,为了解决一些问题突破 “常识”,可能带来的问题反而会更大。

因此,本文的目的是,通过搜索引擎和一些论证,验证字符串枚举类型在大多数场景下:其优点明显大于缺点 且 明显优于数字枚举类型。

注:

  1. 本文仅讨论数据库枚举类型存储方案的两种方式:字符串和数字类型的优劣,不讨论什么时候需要将枚举抽象成表再辅以外键的方案,也不讨论使用 MySQL 原生 enum 类型。
  2. 作者虽然会努力的从客观的角度来论证,但是可以看出本文是预设了答案,然后搜寻证据的,所以难免有不客观的地方。请以自己理解为准,也欢迎讨论。
  3. string 和 int 真得重要吗?我认为涉及到研发人员的工作效率(体现在可读性、可运维性)的内容,都是关系到研发人员切身利益的事情,不可谓不重要。

性能分析

在由 Donald Knuth 在 1974 写的著名的 Structured Programming with go to Statements 一文中提到了 “过早优化” 的问题:

性能瓶颈(这个概念)被滥用已是不争事实。 开发者们浪费了大量的时间去思考它、担心它,(例如)非关键代码上的运行速度。这些对效率的苛求,给调试与维护造成了很大的负面影响。我们理应忽略那小部分的效率,就拿达到 97% 而言,过早的优化是万恶之源。

虽说我们不应放弃优化那 3%,一个好的程序员不会因为这个比例小就放弃(译:指在开发意识上,对于效率的高度追求),而是会明智地观察和识别哪些是关键的代码。

使用数字类型而不是字符串类型的一个主要原因是:数字类型的性能好、字符串类型的性能差。本部分论证的是:

  • 数字类型的性能好、字符串类型的性能差,真得有那么显著吗?针对这一点,分别从时间和空间的角度分析。
  • 另一方面,我们需要认识到,即使 string 的性能差一点,但是这真的会是系统的核心瓶颈吗?针对这下文会给出分析。

时间

在网络上可以搜到一些对 MySQL 的 int、char、varchar 的字段的时间效率上进行分析的报告,这里引用一下 《MySQL中int、char、varchar的性能浅谈》 这篇文章的结论:

  • 无索引:全表扫描不会因为数据较小就变快,而是整体速度相同,int/bigint作为原生类型稍快12%。
  • 有索引:char与varchar性能差不多,int速度稍快18%

由此可以看出,数字类型在时间上优势并不明显。

空间

如果使用 tinyint 类型,则只占 1 个字节,而字符串类型则会占用空间是定义的枚举变量的字符串长度,一般 2~10 个字符即 2~10 字节,因此存储空间上:字符串类型空间占用是 tinyint 的 2~10 倍。

看起来快达到一个数量级了。但是,从如下两个角度来看,数字类型的优势并不明显

  • 枚举字段在一张表中的占比一般很小,如果一个表每 10 个字段有 1 个枚举字段,字符串类型增加的存储空间,在整张表中,也不会增加 10%。
  • 在所有资源类型中,磁盘价格是最廉价的,在这个大数据时代,OLTP 系统所占磁盘的成本,在系统的所有成本中,占比基本可以忽略不计。而人力又是最贵的,为此浪费时间,简直是得不偿失。

性能核心瓶颈

换个角度来说,即使在时间和空间上,字符串的性能都比数字类型若1个数量级。但是,这真得是系统的核心瓶颈吗?

这很难直接给出明确答案。但是,我们在优化系统性能的过程中,真得有考虑过,通过改把表字段的类型从字符串改为数字解决的吗(系统严重设计缺陷除外)?

可维护性

总的来说,字符串能提供人类可读的信息,而数字本事是无意义的。

数据库可读性

代码可读性的重要性,这是所有开发人员都能达成的共识。但是数据库是否需要可读性呢?我认为同样重要,甚至更重要。

软件工程理论学科已经给出了一个结论:软件维护阶段的成本在软件生命周期中是最高的。因此在设计可维护性是系统设计的重中之重,而数据库在软件维护阶段是比源代码更频繁打交道的对象。因此数据库可读性的从这个角度来看,比代码可读性还要重要。

因此,在数据库中使用数字类型的枚举,在数据库可读性上来说,简直是一场灾难。背景中已经详细描述了相关说明。

源代码可读性

在 Go 语言中,不管使用字符串还是数字类型枚举,都会定义常量,源代码可读性两者没有区别。

沟通学习成本

使用 数字 类型,沟通成本会异常的高,主要原因是:对于需要理解这些枚举的人来说,都要记录/记忆数字和真实含义的对应关系,这样的学习成本和理解成本是累计起来是不可小觑的。

而对于 字符串 类型,其含义是不言自定的,只要单词准确,人的学习和沟通成本会降低很多。

可扩展性

枚举类型的可扩展性

调整顺序

数字类型枚举在信息熵提供额外的没必要的顺序性,很有可能会错误的依赖了这个顺序性,这极大的破坏扩展性。

随着业务的发展,可能需要需要在枚举类型中某值中间添加一个值。

如果我们使用的是数字类型,且枚举类型在业务上是有顺序的,这样我们就会得到一个数字顺序和业务顺序不一致的情况,这有如下两个问题:

  • 逼死强迫症
  • 如果代码中有隐式依赖数字顺序的场景,比如 enum < 某这个值,这样就要仔细的修改代码

而字符串类型没有提供而外的顺序信息

枚举值语义扩展、收缩和重定义

枚举值使用数字一个可能的好处是,数字是无意义的,随着业务的发展,枚举值的意义可能发生变化。

具体分析下,数字类型可能出下如几种情况:

  • 语义扩展,该值比之前更多的含义,使用数字类型只需重命名常量命名即可
  • 语义收缩,该值的含义比之前减少,使用数字类型只需重命名常量命名即可
  • 重定义,该值的含义和之前的完全不一样,使用数字类型只需重命名常量命名即可

确实,如果使用字符串类型,枚举值的语义已经被字符串的值绑定了,因此就没办法修改这个值的含义了,确实是限制了可扩展性。

但是,换个角度来向,更改原有的值的语义真得的是一个好的设计吗,好的设计应该符合不可变原则的。该类型是可以通过,废弃和新增枚举值来实现,只是这种方式开发成本可能会高一点,但是也更容易测试,发生事故的可能性会低一些。

最终结论

从上文可以看出,采用字符串类型枚举,其优点明显大于缺点 且 明显优于数字枚举类型。

但是,还是分场景给结论。

特殊场景

如下特殊场景还需需要使用数字类型:

  • 高流量的 C 端业务(用户量至少千万)
  • 业务极具变化,极度不稳定,不重视设计

多数场景

其他场景,建议直接使用 字符串 类型枚举。比如:ToB(企业) 或者 ToD(研发) 的系统。

引用