• 数据库设计规范


    现代软件架构的复杂性要求需要多人或多团队协同完成开发。在这种背景下,如何高效地协同完成软件的开发呢?对软件工程来说,数据库设计规范是在数据库设计层面对软件开发者的规范或标准。对软件来说,适当的规范和标准绝不是为了消除代码内容的创造性、优雅性,而是限制过度个性化,并以一种普遍认可的统一方式一起做事,提升协作效率,降低沟通成本。代码的字里行间流淌的是软件系统的血液,质量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升系统稳定性,码出质量。
    接下来将从多个方面介绍数据库设计规范。如命名风格、注释风格、SQL编写等。 需要说明的是,这里的数据库特别指MySQL数据库,但其设计思想可用于其他数据库。

    命名风格

    好的命名风格可以让代码更容易理解、维护、使用。数据库设计先从命名风格上进行培养。

    【强制】标识符仅能使用字母、数字、下划线,且不能以下划线开头

    这个规范是标识符定义规则。建议控制标识符长度不超过31个。如果太长,则需考虑换种角度定义标识符,如使用缩写。

    【强制】数据库名、表名、字段名等必须使用小写字母

    Windows 操作系统不区分大小写,而 Linux 和 Unix 等操作系统是区分大小写。因此,数据库名、表名、字段名等,都不允许使用大写字母。正例:user,money;反例:User,Money。

    【强制】数据库名、表名、字段名等命名时使用snake风格

    在定义数据库名、表明、字段名、索引名等时,统一使用蛇形命名法(snake case),即用下划线将单词连接。示例:user_name, create_time,update_time等。

    【强制】空白字符只能是空格,不能使用制表符

    禁止使用制表符。不同操作系统对制表符的转码不同。不建议使用Tab缩进。如果使用 Tab 缩进,必须设置 1 个 Tab 为 4 个空格。

    【强制】禁止使用拼音与英文混合方式命名或直接使用中文的方式命名

    基于英文编码已是事实标准。在定义标识符时,不应将拼音与英文混合或直接使用中文。对国际通用的拼音,可以视同为英文,如BeiJing等。

    【强制】数据库名、表名、字段名等不应使用复数形式

    数据库名、表名、字段名等仅表示实体或实体的属性,不应该表示实体数量。正例:user、apple;反例:users、apples。

    【强制】禁止在命名中使用关键字、保留字

    禁止在数据库名、表名、字段名等命名时,直接使用关键字、保留字。关键字在SQL中具有重要意义的词。某些关键字,如SELECT, DELETE等,被保留,需要用作标识符。保留字则是预留的一些字段,可能会在未来的版本中使用。MySQL的保留字很多,具体可以参考官网。(在定义数据库名、表名、字段名、索引名等时,通常情况下,SQL编辑器会高亮这些特殊单词)。
    注意,如果将保留字拼接到名称中,是允许的。如internal是保留字,但是在字段定义时,是可以定义类似"internal_time"这种字段名称的。

    【强制】禁止使用任何语言的种族歧视性词语

    随着种族意识的弱化,不应使用使用种族歧视性词语。正例:block_list、allow_list、secondary等;反例:black_list、white_list、slave 等。

    【强制】约束、索引、触发器等缩写统一

    在定义表名、主键、外键、唯一索引、普通索引等,需要使用同一的缩写(不同厂商使用的缩写不一定相同,只要保证内部统一即可)。常见的缩写有:

    名称缩写说明
    表名t英文table,表名一般定义为"t_系统名称缩写_业务名称"。示例:t_xxx_user,t_xxx_money
    主键pk英文primary key,主键名一般定义为"pk_表名"。示例:示例:pk_user,pk_money
    外键fk英文foreign key,外键名一般定义为"fk_+从表名+_+主表名"。示例:t_money_user
    唯一键uk英文unique key,唯一键一般定义为"uk_+索引名"。示例:uk_money
    普通索引idx英文index,索引名一般定义为"index_+索引名"。示例:idx_money
    触发器trg英文index,索引名一般定义为"trg_+索引名"。示例:trg_money_xxx_year
    视图view英文index,索引名一般定义为"view_+索引名"。示例:view_sale_money
    函数func英文index,索引名一般定义为"func_+索引名"。示例:func_update_money
    存储过程proc英文index,索引名一般定义为"proc_+索引名"。示例:proc_store_money

    【推荐】数据库的命名规则统一

    一般情况下,一个大的业务系统包含多个子系统,每个子系统使用独立的数据库。对于这种情况,需要统一子系统的数据库的命名规则。其规则如下:业务系统名称_子系统名,同一模块使用的表名尽量使用统一前缀。示例:health、health_log、health_trade。

    整体设计

    【强制】存储引擎使用INNODB

    数据库存储引擎统一使用 INNODB,除非读写比率<1%,才考虑使用 MYISAM 存储引擎。相比MYISAM,INNODB 在可靠性和性能方面体现出极大优势。其他存储引擎或其他数据库的存储引擎请在数据库专家的建议下使用。
    尽管在MySQL 5.5 版本以后,默认存储引擎就是 InnoDB,但是在DDL中尽量显式指定存储引擎。示例:

    CREATE TABLE IF NOT EXISTS tab_user(
      pk_id int NOT NULL AUTO_INCREMENT COMMENT '唯一主键',
      user_name varchar(128) NOT NULL COMMENT '用户名称',
      create_time datetime NOT NULL COMMENT '创建时间',
      update_time datetime NOT NULL COMMENT '更新时间',
      PRIMARY KEY (`pk_id`)
    )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    【强制】数据库或表字符集统一使用 utf8 或 utf8mb4

    统一字符集可以避免由于字符集转换产生的乱码,不同的字符集进行比较前需要进行转换会造成索引失效,如果数据库中有存储emoji(表情)的需要,字符集需要采用utf8mb4字符集。示例如下:

    CREATE DATABASE IF NOT EXISTS db_name DEFAULT CHARSET utf8mb4;
    
    • 1

    【推荐】谨慎使用 MYSQL INSIDE 函数

    以 UUID(),USER() 等函数为例,其对于复制来说是很危险的,会导致主备数据不一致。

    【强制】谨慎使用外键约束

    如果数据存在外键关系,请在程序层面实现。尽管外键能够保证主从表的数据完整性,但会给系统带来一定影响:(1) 性能方面,外键约束会降低从表的写入速度(在写入从表前,还需确认主表是否存在对应的数据);(2) 并发方面,外键约束会增大高并发场景下出现死锁的情况(使用外键约束后,需要额外查询主表,增加出现死锁的可能);(3) 可扩展性方面,对于平台迁移场景(如从Mysql迁移到Oracle),不同数据库平台的实现有差异。对于分库分表场景,在水平拆分和分库的情况下,外键是无法生效的。
    将外键约束在应用程序中实现,虽然增大了开发人员的工作量,但是却减少了数据库层面的压力。而且应用程序的处理能力还可以通过水平扩展的方式增强,这种还是很推荐的。
    当必须使用外键的话,必须在外键列创建索引。

    【建议】尽量不用或少用视图、存储过程、函数、触发器

    除非有明确的需求,否则不建议使用视图、存储过程、函数、触发器。对 MYSQL 来说, 视图、存储过程、函数、触发器 还不是很成熟,没有完善的出错记录处理。此外,视图、存储过程、函数、触发器会对读写性能产生影响,应明确风险后,再使用。

    【推荐】单表行数超过 500w 行或者单表容量超过 2GB,才推荐进行分库分表

    MySQL在处理大表时,性能就开始明显降低。如果一个表超过 500w 行,那么该表的读写速度将大打折扣。如果一个表预计两年后的数据量根本达不到这个级别,请不要在创建表时就分库分表(避免过度设计)。
    500w 行只是一个参考值,不同厂商定义的规格不要求完全一样,根据实际测试情况来规定阈值。如阿里的标准就是500W,百度的确是2000W。

    【强制】对超过100W行的大表进行alter操作,必须谨慎,并在业务低峰期执行

    以 alter table为例,会产生表锁,执行期间会阻塞对于该表的所有写入,对于业务可能会产生极大影响。

    【推荐】字段允许适当冗余,以提高查询性能,但必须考虑数据一致

    冗余字段应遵循:(1) 不是频繁修改的字段;(2) 不是 varchar 超长字段,更不能是 text 字段。

    【推荐】当一个表的字段数非常多时,可以将表分成两张表,一张作为条件查询表,一张作为详细内容表

    当一个表的字段过多时(如大于50个),可以考虑拆分为两个表。其中主表包含常用的一些字段,子表包含使用频率低或占用存储较多的字段(如text、blob、clob等)。

    表设计

    表设计应至少满足第三范式,尽量减少数据冗余

    一些特殊场景允许反范式化设计,但在评审时需要对冗余字段的设计给出解释。

    【推荐】字段尽量设置为 NOT NULL,如果为NULL,建议为其设置默认值

    保证字段非空。如为字段提供默认值。因为使用空值会带来聚合函数计算结果偏差等问题。此外,不同数据库对空值的处理不同,使用空值还需在数据迁移时额外处理。

    【建议】推荐使用 IS NULL(cloumn) 来判断 NULL值

    如果字段可能为空,在执行判空时,要使用IS NULL等函数,不能使用=或==判断。

    【强制】表必备三字段:pk_id, create_time, update_time

    id为主键,类型为unsigned bigint(禁止使用int)、单表时自增、步长为1。小型系统可以依赖于 MySQL 的自增主键,大型系统或者需要分库分表时才使用内置的 ID 生成器。主键的优势是唯一标识、有效引用、高效检索,所以一般情况下尽量有主键字段。
    create_time, update_time 的类型均为 date_time,前者表示创建时间,后者表示更新时间。对表的记录进行更新的时候,必须包含对 update_time 字段的更新。

    【推荐】单表字段数不要太多,建议最多不要大于50个

    过度的宽表对读写性能产生很大的影响。

    需要join的字段(连接键),数据类型必须保持绝对一致,避免隐式转换

    如关联的字段都是int类型。

    字段设计

    【强制】一个字段只表示一个含义

    不要引入复合字段。字段的含义应该是明确的,保证字段的原子性。

    【强制】文本数据尽量用varchar存储

    相比char,varchar是变长存储,更省空间。注意,使用varchar类型时,字符数不要超过2700。MySQL 规定一行所有文本最多存65535字节,因此在utf8字符集下最多存21844个字符,超过会自动转换为mediumtext字段。而text在utf8字符集下最多存21844个字符,mediumtext最多存224/3个字符,longtext最多存232个字符。
    如果存储长度大于2700,可将该字段定义为 text,并独立出一张表,避免影响其它字段索引效率。

    【强制】金融场景,小数类型为 decimal

    float 和 double 在存储的时候,存在精度损失的问题。decimal本质上字符串存储。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。

    【建议】不推荐使用 enum 或 set 类型

    enum 或 set 类型不仅浪费空间,且枚举值写死了,变更不方便。推荐使用smallint或varchar类型。

    【建议】不推荐使用clob, blob, text 类型

    clob, blob, text 等类型比较浪费硬盘和内存空间。在加载表数据时,会读取大字段到内存里从而浪费内存空间,影响系统性能。在使用这类字段前,建议做好相关评审工作,明确是否真的需要这类字段。
    Innodb中当一行记录超过8098字节时,会将该记录中选取最长的一个字段将其768字节放在原始page里,该字段余下内容放在overflow-page里。不幸的是在compact行格式下,原始page和overflow-page都会加载。

    索引设计

    【推荐】索引存储结构推荐BTREE

    InnoDB和MyISAM存储引擎表,索引类型必须为BTRER,MEMORY表可以根据需要选择HASH或者BTREE类型索引。

    【建议】单个表上的索引个数不能超过7个

    索引在加速查询的同时,也会带来写入速度降低的问题(写入数据的同时,要更新索引)。应限制单表上索引个数。

    【强制】使用索引时,要考虑索引生效、失效的场景

    索引在sql中的where子句、order by子句、join子句、select子句中会生效。但是,也要注意索引不生效的情况,如:
    (1) 查询条件中有or。此时,必须保证所有or相关的字段都有索引才能生效。所以要谨慎使用or语句。(2) like语句以%开头。模糊查询时,使用%且将其放在开头,会导致索引失效。(3) 如果存在类型转换(如存储是字符串,查询的时候未用引号,则会进行类型转换),索引会失效。(4) 索引列参与计算会导致索引失效(如执行算数运算或使用函数)。(5) 违背最左匹配原则。(6) 如果Mysql 评估全表扫描要比使用索引要快,则索引失效(数据量很少,使用索引不会提升查找性能)。

    SQL操作

    【强制】避免使用select * 语句

    select * 会返回该表的所有字段。对于宽表来说,查询所有字段是一种灾难,因为获取一些根本不需要的字段是没有必要的。此外,join语句使用select *可能导致只需要访问索引即可完成的查询需要回表取数。

    【强制】严禁使用 select 语句时,不添加where子句

    不加where条件,会变成全表扫描。

    【强制】where语句中的过滤条件字段上严禁使用任何函数,包括数据类型转换函数

    大量的计算和转换会造成导致索引失效,当数据量很大时,会带来性能问题。

    【强制】分页查询语句都要带有排序条件, 否则会引起乱序

    【建议】用in()/union替换or

    or 会导致索引失效(or关联的所有字段存在没有索引的时候),为保证效率,可以使用in或union。注意 in 会带来性能问题,如果包含的元素是不固定的时候。建议in中元素的个数小于100(经验值)。

    【强制】严禁使用%前缀进行模糊前缀查询

    在like子句中使用%做前缀会导致索引失效。可以使用%模糊后缀查询。

    【强制】禁止使用不含字段列表的insert语句

    使用 insert时,要指定插入的列。示例:insert into t_name(col1,col2,col3) values (‘a’,1,‘text’)

    【强制】大批量写操作(UPDATE、DELETE、INSERT),需要分批多次进行操作

    大批量操作可能会造成严重的主从延迟,特别是主从模式下,大批量操作可能会造成严重的主从延迟,因为需要slave从master的binlog中读取日志来进行数据同步。binlog日志为row格式时会产生大量的日志。

    【建议】统计行数时,推荐使用count(*)且不带where条件

    基于count()函数统计行数时,count(*)和count(1)都可以用来统计表中的行数,基于SQL规范考虑,推荐使用count(*)。注意,在使用count(*)时,要注意不同存储引擎的支持事务的水平,如MyISAM不支持事务,使用的锁是表级锁,不会有并发的行操作,所以查询的结果是准确的。InnoDB支持事务,并且支持行级锁,行可能被并行修改,那么缓存记录不准确。且不加where条件时,MyISAM 引擎会直接返回这个总数。
    不推荐使用 count(column_name),因为count(column_name)只能统计列值非NULL的行数。且会基于该字段是否使用了索引,决定是否基于索引统计,还是全表扫描统计。

    注释风格

    注释主要有以下三方面的作用:1.能够反映数据库的设计思想和主要功能;2.能够描述业务含义,方便其他程序员了解代码背后的含义;3.提示示例代码,方便调用者使用。对于注意的中英文没有特别要求,对于面向国内开发者或公司内部的项目,其注释推荐使用中文,对于面向国际环境或开源的项目,其注释推荐使用英文。

    【推荐】表和字段使用清晰的注释

    在定义表时,使用COMMENT关键字添加字段说明。示例如下:

    CREATE TABLE IF NOT EXISTS tab_user(
      pk_id int NOT NULL AUTO_INCREMENT COMMENT '唯一主键',
      user_name varchar(128) NOT NULL COMMENT '用户名称',
      create_time datetime NOT NULL COMMENT '创建时间',
      update_time datetime NOT NULL COMMENT '更新时间',
      PRIMARY KEY (`pk_id`)
    )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    对于其他场景,使用SQL注释。SQL 接受的注释有三种:

    (1) -- 这儿是注释 (注意,第 2 个破折号后面至少跟一个空格符) 
    (2) /* 这儿是注释 */
    (3) # 这儿是注释
    
    • 1
    • 2
    • 3

    参考

    数据库设计开发规范 阿里云.数据库服务部 2010
    https://cloud.tencent.com/developer/article/1475852 MySQL数据库设计规范
    https://www.mysql.com/products/enterprise/techspec.html MySQL Technical Specifications
    https://www.itworkman.com/mysql-database-design-specifications/ MySQL database design specifications
    https://blog.csdn.net/dear_little_bear/article/details/108511836 MySQL设计规范及性能优化
    https://zhuanlan.zhihu.com/p/171764482 InnoDB存储引擎简介
    https://www.jianshu.com/p/f2db3010938f Mysql 关键字 保留字
    https://blog.csdn.net/qq_43842093/article/details/127168632 mysql 命名规则
    https://juejin.cn/post/7073649358498955295 数据库规范
    https://zhuanlan.zhihu.com/p/391673897 索引设计(索引设计详细规范)
    https://zhuanlan.zhihu.com/p/62020571 为什么不推荐使用外键约束
    https://blog.csdn.net/sy_white/article/details/122112440 索引失效的情况及解决(超详细)

  • 相关阅读:
    阿里二面:Java中锁的分类有哪些?你能说全吗?
    【游戏引擎之路】登神长阶(七)——x86汇编学习:凡做难事,必有所得
    Ajax跨域访问,访问成功但一直走error不走success的的问题解决
    【计网】(二)MAC地址与IP地址
    算法 链表内指定区间反转
    flask旅游景点推荐
    MySQL有什么推荐的学习资源?
    Unity实战(11):项目非启动状态下使用代码批量替换材质
    c++ 聚合/POD/平凡/标准布局 介绍
    【BUG】第一次创建vue3+vite项目启动报错Error: Cannot find module ‘worker_threads‘
  • 原文地址:https://blog.csdn.net/wangxufa/article/details/127718833