一、设计规范

1.1.是否需要自增ID?

数据库表,一定要有id,而且要用自增id!

有些人喜欢用自定义的,用UUID或者其他七七八八的id,如果在架构设计,代码比较好的情况下,不会出啥大问题,但是一旦代码写的不行,极有可能就造成id重复之类的问题。

自增id另外还有一个好处,就是在数据迁移的时候,分页查询通过id来进行分页,速度会比传统分页快很多。

这个字段还是要有的,但是强烈建议不要在删除行数据,查询数据,修改数据时使用到该字段,因为该字段的单独操作会破坏掉数据的隔离性。也就是前面所说的,所有的sql操作,都要带上租户id再进行。

自增ID规范
  • 则表中的第一个id字段一定是主键且为自动增长;
  • 建议主键是整型,最好是unsigned bigint类型,避免后续需要扩展
  • 为主键选择更有意义的名称,如ID这个名称太过笼统,表达的信息可能不准确。

1.2是否使用备用字段?

在数据表中,不仅设计了当前所需要的字段,而且还在其中留出几个字段作为备用。
比方说,设计了一个人员表(Person),其中已经添加了各种必要的字段,包括姓名(Name)、性别(Sex)、出生年月日(birthday)等等。大功告成之后,忽然想到,将来系统中应该还会有很多其它与人相关的内容吧,比方说毕业院校,比方说工作单位等等,尽管现在根本不需要填写,以后可能还是会用到的吧。拍脑袋一项,那就加入5个varchar2型的字段,分别叫做Text1、Text2……Text5,然后又想,应该还有一些日期型的字段需要备用,就又建立了三个date型的字段,分别起名叫做date1、date2、date3。

【解决方案】

其实上面的这种设计方式就是一种“过度设计”,我们应该做的就是“按需设计”。

因此要禁止在表中建立预留字段,理由如下:

1.无法准确的知道预留字段的类型,所以无法选择合适的类型。

2.无法准确的知道预留字段中所存储的内容,预留字段的命名很难做到见名识义

3.后期维护预留字段所要的成本,同增加一个字段所需要的成本是相同的。对预留字段类型的修改,会对表进行锁定。(修改一个字段的成本,大于新增字段)。

推荐解决办法是,当需要增加相关的信息的时候:

  1. 如果数量很少,而且信息的性质与原表密切相关,那么就可以直接在原表上增加字段,并将相关的数据更新进去;
  2. 如果数量较大,或者并非是原表对象至关重要的属性,那么就可以新增一个表,然后通过键值连接起来;
  3. 对于表的数据的存储位置所导致的性能问题,我们可以通过在特定时间对数据库的数据进行重组来解决,而这项工作对于长期运行的数据库来说,也是需要定期进行的。

1.3是否使用外键、触发器和存储过程?

外键、触发器不要有。 数据的完整性靠程序来保证。

触发器和存储过程容易将业务逻辑和DB耦合在一起。

有了外键、触发器,你会发现: 写代码不方便。 订正数据不方便。 迁移数据也麻烦。 总之,你要是坚持用,后续的坑等着你。

虽然不建议使用外键约束,但是相关联的列上一定要建立索引。

1.4.表应该具备哪些字段?

自增ID

id必为主键,类型为unsigned bigint、单表时自增、步长为 1。若业务场景需要或未来有分库分表扩展需求,类型为unsigned bigint,建议采用唯一id设计,如SnowFlake雪花ID算法,请最后考虑UUID。

创建时间&修改时间

创建时间create_time和修改时间update_time这两个字段,每个表都必须有! 数据类型为datetime
注意,一定要用数据的时间戳,自动生成。不要通过代码去操作这两个字段。推荐使用AOP去自动处理。

有了这两个字段。你可以追溯到数据的时间点,创建和修改的时间点。极大方便你在某些情况下的排查数据问题。

建议时间精确到毫秒级别,因为在大数据量的情况下,可能一秒有几十、几百、上千、上万的数据新增都是有可能的。那么秒级在这种情况下完全就不够看了,选择毫秒级别是一个比较好的选择。

创建人&修改人

创建人create_by和修改人update_by两个字段,每个表也都必须有。数据类型为unsigned bigint(关联用户id)或varchar(关联登录名,确保不变)
还是和前面一个原因,出问题的时候可以追溯起因,否则遇上日志过久无法查看或者其他原因出现未知数据,都不知道数据怎么来的,需要花非常大的代价查看日志、代码等。

软删除

只能逻辑删除,不能物理删除,重要!!!

CREATE TABLE `database_name`.`table_name`  (
  `id` bigint(18) UNSIGNED NOT NULL COMMENT '雪花算法id IdGenerate.generateId();',
  `create_by` varchar(20) DEFAULT NULL COMMENT '创建人',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_user` varchar(20) DEFAULT NULL COMMENT '修改人',
  `update_by` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '逻辑删除标志0有效,1无效',
  PRIMARY KEY (`id`)
);

1.5范式与反范式

尽量遵守第三范式的标准(3NF):表内的每一个值只能被表达一次;表内的每一行都应当被唯一的标示;表内不应该存储依赖于其他键的非键信息。

表与表之间的信息,用id进行关联,尽量不要有冗余的信息数据,否则你需要更新同一份信息的时候,需要更新多个地方。

但是在某些情况下,你确认信息不会经常变动,且该信息确实在两个表中都有会比较好,那么,放心的去冗余吧。但是注意,数据的更新用上事务。

查多改少的场景,适合用字段冗余。还是上面的例子:订单详情的查询很多,但是商品名称的修改很少,适合将商品名称冗余到订单表中

字段允许适当冗余,以提高查询性能,但必须考虑数据一致性。冗余字段应遵循以下原则。
1)不是频繁修改的字段。
2)不是唯一索引的字段。
3)不是varchar超长字段,更不能是text字段。

1.6存储引擎和国际化

所有表必须使用Innodb存储引擎,5.6以后的默认引擎,支持事务,行级锁,更好的恢复性,高并发下性能更好。如无说明,建表时一律采用innodb引擎;

数据库和表要使用统一的字符集(如:UTF8),统一字符集可以避免由于字符集转换产生的乱码,MySQL中的UTF8字符集汉字点3个字节,ASCII码占用1个字节。
字符集:utf8mb4、排序规则:utf8mb4_general_ci

1.7 关于分库和分表

当单表行数超过500万或者单表容量超过2GB时,才推荐分库分表。

二、命名规范

2.1数据库表名规范

(1)表名、字段名必须使用小写字母或数字

MySQL在Windows系统中不区分大小写,但在Linux系统中默认区分大小写。

正例:aliyun_admin,level_name
反例:AliyunAdmin,levelName
(2)表名不使用复数名词。

表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应到DO类名也是单数形式,符合表达习惯。

正例:user,employee
反例:users,employees
(3)表的命名最好遵循“业务名称_表的作用”原则
正例:alipay_task,trade_config
反例:yy_all_live_category、yy_alllive_comment_user。
//说明:去除项目名,统一命名规则,均为”yy_alllive_”开头即可。
(4)表的名称一般使用名词或者动宾短语(动宾逻辑顺序统一)。
错误示例:yy_showfriend、yy_user_getpoints、yy_live_program_get。
//说明:去除项目名,统一命名规则,动宾短语分离且动宾逻辑顺序统一。
(5)表名尽量要用英文单词的全拼

不要自己对英文单词进行缩写,也不要使用汉语拼音和小众的英文缩写。可以使用常见的其意义被大众熟知的英文缩写或英文字典中定义的缩写。

数据库对象的命名要能做到见名识义,并且最好不要超过32个字符。

表名称不应该取得太长(一般不超过三个英文单词)

(6)明细表的名称为:主表的名称+字符dtl(detail缩写)

例如:采购定单的名称为:po_order,则采购定单的明细表为:po_orderdtl。

(7)临时库/表必须以tmp为前缀并以日期为后缀。
(8)备份库/表必须以bak为前缀并以日期为后缀。
(9)通用表要加前缀“all_”,示例:all_user。
(10)表必须填写描述信息(使用SQL语句建表时)
(11)一个项目一个数据库,多个项目慎用同一个数据库。

2.2数据库字段命名规范

(1)字段必须填写描述信息。
(2)当修改字段含义或追加字段表示的状态时,需要及时更新字段注释。
(3)多个单词使用下划线’_'分隔

采用26个英文字母(区分大小写)和0-9的自然数(经常不需要)加上下划线’‘组成,命名简洁明确,多个单词用下划线’'分隔。

反例:username、userid、isfriend、isgood。
//说明:使用下划线进行分类,提升可读性,方便管理
//修改为“user_name”、 “user_id”、 “is_friend”、 “is_good”。
(4)全部小写命名,禁止出现大写。
反例:userID、houseID。
//说明:使用统一规则,修改为“user_id”、“house_id”。
(5)禁用保留字,如name、desc、range、match、delayed等。

请参考MySQL官方保留字。

(6)字段名称一般采用名词或动宾短语。
名词示例:user_id、user_name、sex;动宾短语示例:is_friend、is_good。
(7)命名字段时要用英文单词的全拼

不要自己对英文单词进行缩写。也不要使用汉语拼音和小众的英文缩写。可以使用常见的其意义被大众熟知的英文缩写或英文字典中定义的缩写。

字段命名使用完整名称,禁止缩写

反例:uid、pid。
//说明:使用完整名称,提高可读性,修改为“user_id”、“person_id”。
(7)禁止在命名字段时,重复表的名称

例如,在名employe的表中禁止使用名为employee_lastname的字段。

(8)禁止在命名字段时,包含数据类型。
(9)表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是unsigned tinyint(1表示是,0表示否)。

说明:任何字段如果为非负数,则必须是unsigned。注意:POJO类中的任何布尔类型的变量,都不要加is前缀,需要在中设置从is_xxx到xxx的映射关系。数据库表示是与否的值,使用tinyint类型,坚持is_xxx的命名方式是为了明确其取值含义与取值范围。

正例:表达逻辑删除的字段名is_deleted,1表示删除,0表示未删除。

2.3数据库字段类型规范

(1)如果存储的字符串长度几乎相等,则使用char定长字符串类型。

例如,11位手机号,邮编(postcode)。

这种固定长度的纯数字,也不要用int类型或long类型,因为只有数字参与了运算,才用数值型。

(2)小数类型为decimal,禁止使用float和double类型。

在存储时,float和double类型存在精度损失的问题,很可能在比较值的时候,得到不正确的结果。如果存储的数据范围超过decimal的范围,那么建议将数据拆成整数和小数并分开存储。

(3)整型int定义中不添加长度

比如使用INT,而不是INT(4)。

(4)varchar是可变长字符串,不预先分配存储空间

禁止使用varchar类型作为主键。

长度不要超过5000个字符,如果存储长度大于此值,则应定义字段类型为text,独立出来一张表,用主键来对应,避免影响其他字段的索引效率。

(5)禁止使用blob、text类型保留大文本、文件、图片

建议使用其他方式存储,MySQL只保存指针信息。

一列需要占很大空间的字段,一定要单独拎出来,不要和常用信息放一张表。

举个例子: 文章的信息和文章的内容,这一定要分成两个表。否则会给你的文章性能带来极大的挑战。因为很多情况下,查看文章列表,根本不需要查看到文章的内容。

(6)所有字段在设计时,必须有默认值

字符型的默认值为一个空字符值串‘’,数值型的默认值为数值0,逻辑型的默认值为数值0。

系统中所有逻辑型中数值0表示为“假”,数值1表示为“真”,datetime、smalldatetime类型的字段没有默认值,必须为NULL。

除以下数据类型timestamp、image、datetime、smalldatetime、uniqueidentifier、binary、sql_variant、varbinary外

(7)IP地址使用unsigned int类型。

这样比较节约存储空间

select  INET_ATON('192.0.0.0')
select INET_NTOA(3221225472)
(8)避免使用NULL字段

NULL字段很难查询优化、NULL字段的索引需要额外空间、NULL字段的复合索引无效

(9)多表中的相同列,必须保证列定义一致。
(10)不建议使用ENUM类型,使用TINYINT来代替。

1)、假如一个设计不合理的ENUM字段,给程序员带来的就完全是梦魇了,比如一个enum字段的范围是(‘0’,‘1’,‘2’,‘3’,‘4’,‘5’),而enum的枚举值对应的索引是从1开始的,因此,insert into table (enum)values(1),插入的并不是1,而是0

(2)、另外假如你在设计好enum的枚举字段范围并使用了一段时间后,再到字段范围中加一个枚举值,并且不是加在最后,那么也就相当于把原来的范围都改变了索引值,也就是当你在查询的时候直接查询值(并加上单引号),将不会使用enum自身隐藏的索引值来获取结果了

(3)、如果是纯数值型,还是建议采用tinyint字段吧,毕竟它也只占一个字节,即使出现赃数据,也可以被接受,不象enum,如果纯数字型范围,更改了索引,你就不知道你查询的值是否正确了

(4)、如果字段是字符串,并且长度固定,可以尝试用char,如果是数值型,还是用tinyint吧,比较安全稳定,而且即使迁移,也不会出现太多问题

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐