数据库设计规范

一、基本命名原则

1.1 字符范围

只能使用26个英文字母、下划线(_)、数字(0-9)进行命名,且首位字符必须是英文字母。

1.2 字母全部小写

所有数据库对象命名字母全部小写,统一大小写有助于在多数据库间转移。

1.3 命名长度

库名、表名、字段名称长度不要超过32个字符。

1.4 分段命名

命名中多个单词间采用下划线分割,以便阅读同时方便某些工具对数据库对象的映射。并且禁止出现数字开头,禁止两个下划线中间只出现数字。

1.5 不用保留字

数据库对象命名不能直接使用数据库保留关键字,但分段中可以使用。如user不能用于表名、列名等,但是user_name可以用于列名,user_info也可以用于表名。(Mysql8.0的754个关键字其中262个保留字,详见:MySQL 8.0 Keywords and Reserved Words

1.6 同义性

对于同一含义尽量使用相同的单词命名,不管使用英文单词、英文缩写还是拼音首字母,以免引起误解。如telphone在A表中表示移动电话号码,在B表中就不应该用于表示固定电话号码。尽量避免同一单词表示多种含义的情况。

1.7 命名方式一致

在一个系统、一个项目中尽量采用一致的命名方式,都采用英文单词或者拼音首字母。尤其要避免在一个对象命名中同时采用英文单词和拼音首字母。如确实需要在一个项目中采用两种命名方式,考虑系统功能设计相关表(开发)使用英文单词命名,业务相关的表(实施)使用拼音首字母。

1.8 类型一致

同一个字段在同一个库的不同表中,字段类型,长度,意义应该一致。

1.9 字符集

创建数据库时可显式指定字符集,只能使用utf8(推荐首选)或utf8mb4(特殊情况下,比如要存储表情符号、罕见字等可以使用)格式,数据库默认字符集为utf8。

二、基本设计规范

2.1 数据库基本设计规范

· 库名建议格式:业务系统名称_子系统(模块)名。

· 备份数据库使用正式数据库名加上备份时间。

· Mysql使用InnoDB存储引擎。

· 数据库的查询QPS过高,就需要考虑拆库,通过分库来分担单个数据库的连接压力。

2.2 数据表基本设计规范

· 表名不推荐使用复数名词,表名应该仅仅表示表里面的实体内容,不应该表示实体数量。

· 表的命名推荐格式:“业务名称_表的作用”。

· 视图以v作为前缀,其他命名规则和表的命名类似,命名应尽量体现各视图的功能。

· 备份数据库表名使用正式表名加上备份时间。

· 表必须有主键,主键名称推荐格式:表名_id(外键格式同主键名,使用其指向的表名_id命名),推荐使用UNSIGNED自增列作为主键。

· 表必须包含创建时间(create_time)、修改时间(modify_time)和删除标记(is_del)字段。

· 使用 comment 属性来描述此表所代表的真正含义。

· 临时表或中间表时,建议以tmp_、bak_、日期等作为表名前缀或后缀区分。

2.3 字段基本设计规范

· 如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。

· 使用 comment 属性来描述此 字段所代表的真正含义。

· 表达是与否概念的字段,数据类型是 unsigned tinyint ( 1 表示是,0 表示否)。任何字段如果为非负数,必须是 unsigned。

· 字段的长度设得尽可能小。

· 用尽量少的存储空间来存储一个字段的数据。

· int(11) 适用于自增主键,时间戳等其它非小数类型的字段。

· 自增列初始值建议设置为1。

· tinyint适用于表示状态或类型的字段。

· datetime适用于时间戳字段。

· varchar(2的n次方) 适用于字符串字段,如果存储的字符串长度几乎相等,使用 char 定长字符串类型。varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。

· decimal(10,6) 适用于小数类型的字段,所有小数类型为 decimal,禁止使用 float 和 double。

· text(2048,4096,……) 适用于大量文本存储字段(尽可能不使用)。

· mediumblob适用于文件图片存储(尽可能不使用)。

2.4 索引设计规范

· 索引名必须全部使用小写,组合索引建议包含所有字段名,过长的字段名可以采用缩写形式。

· 主键索引名为:pk_字段标识,普通索引名为:idx_字段标识,唯一索引名为:uk_字段标识。

· 单张表中索引数量不超过5个,单个索引中的字段数不超过5个。

· 业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。

· 超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致。多表关联查询时,保证被关联的字段需要有索引。即使双表 join 也要注意表索引、SQL 性能。

· 字符类型字段(char,varchar,tinytext,text,mediumtext,longtext等)上不允许建立索引。

· 页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。

· SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 const最好。

说明:

  1. const 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。

  2. ref 指的是使用普通的索引(normal index)。

  3. range 对索引进行范围检索。

反例:explain 表的结果,type=index,索引物理文件全扫描,速度非常慢,这个 index 级别比较 range 还低,与全表扫描是小巫见大巫。

· 防止因字段类型不同造成的隐式转换,导致索引失效。

2.5 SQL语句规范

· 禁止使用存储过程、自定义函数和触发器。

· SQL语句中出现的所有表名、表别名、字段名、序列等数据库对象都应小写。

· SQL语句中出现的系统保留字、内置函数名、SQL保留字、绑定变量等都应大写或小写,严禁使用大小写混合的书写。

· 推荐使用单引号来表示字符值、字符串。

· 缩进应为1个Tab或者4个字符,所有的缩进均为1个缩进量的整数倍,按照代码层次对齐,同层次的SQL语句缩进应保持一致(纵向对齐)。

· 禁止使用SELECT *操作,所有操作必须明确指定列名。

· 使用INSERT语句时,应指定插入的字段名,不应不指定字段名直接插入VALUES。

· 对应的括号要求在同一列的位置上,尤其用在子查询嵌套中。

· 代码中需要添加必要的注释,以增强代码的可读性。

· SQL语句中出现多个表时,给每个表加上表别名。

· SELECT语句查询的字段,推荐将逗号(,)写在字段前面。

· SQL语句尽可能避免多表联合复杂查询。

· 应将SQL语句中的数据库函数、计算表达式等放置在等号右边。

三、关系表设计范式和设计原则

2.1 关系表设计范式

对于数据库设计大有好处。在数据库设计中,为了更好地应用设计范式,就必须通俗地理解设计范式(通俗地理解是够用的理解,并不是最科学最准确的理解):

· 第一范式:1NF是对属性的原子性约束,要求属性具有原子性,不可再分解;

· 第二范式:2NF是对记录的惟一性约束,要求记录有惟一标识,即实体的惟一性;

· 第三范式:3NF是对字段冗余性的约束,即任何字段不能由其他字段派生出来,它要求字段没有冗余。

· BCNF:全称是Boyce Codd Normal Form,是对3NF的补充,若关系模式属于第一范式,且每个属性都不传递依赖于键码,则R属于BC范式。BC范式既检查非主属性,又检查主属性。当只检查非主属性时,就成了第三范式。满足BC范式的关系都必然满足第三范式。还可以这么说: 若一个关系达到了第三范式,并且它只有一个候选码,或者它的每个候选码都是单属性,则该关系自然达到 BC 范式。

· 第四范式:要求把同一表内的多对多关系删除。

· 第五范式:将表分割成尽可能小的块,为了排除在表中所有的冗余。

一般情况下,一个数据库设计符合3NF或BCNF就可以了。并且,某些情况下,过于范式化甚至会对数据库的逻辑可读性和使用效率起到阻碍。数据库中一定程度的冗余并不一定是坏事情。有时为了提高运行效率,就必须降低范式标准,适当保留冗余数据。

2.2 关系表设计原则

· 充分考虑业务逻辑和数据分离

数据库只作为一个保证ACID特性的关系数据的持久化存储系统,尽量减少使用自定义函数、存储过程和视图,不用触发器。

· 组件库间表关联少

应该根据系统架构中的组件划分,针对每个组件所处理的业务进行组件单元的数据库设计;不同组件间所对应的数据库表之间的关联应尽可能减少,确保组件对应的表之间的独立性,为系统或表结构的重构提供可能性。

· 领域驱动设计

采用领域模型驱动的方式和自顶向下的思路进行数据库设计,首先分析系统业务,根据职责定义对象。对象要符合封装的特性,确保与职责相关的数据项被定义在一个对象之内,不会出现职责描述缺失或多余。

· 基本表的范式标准

规范化是关系表设计中的重要概念,它通过将数据分解为更小的关系表,以消除数据的几余和不一致性。在进行规范化时应遵循以下原则:

  1. 第一范式:确保每个字段的原了性,即每个字段只包含一个值。

  2. 第二范式:确保每个非主键字段完全依赖于主键,消除部分依赖。

  3. 第三范式:确保每个非主键字段不依赖于其他非主键字段,消除传递依赖。

基本表及其字段之间的关系,应尽量满足第三范式。但是,满足第三范式的数据库设计,往往不是最好的设计。为了提高数据库的运行效率,常常需要降低范式标准:适当增加冗余,达到以空间换时间的目的。

· 实体和属性的分离原则

关系表应该将实体和属性分离开来,每个实体应该对应一个关系表,每个属性对应表中的一个字段。这样可以保证数据的完整性和一致性。

· 主键的选择原则

每个关系表应该有一个主键,用于唯一标识表中的每一行数据。主键的选择应遵循以下原则:

  1. 主键应该是稳定的,不会随着时间的推移而改变

  2. 主键应该是简洁的,并且易于理解和识别。

  3. 主键应该是唯一的,不会与其他表中的主键冲突。

· 性能优化原则

关系表设计不仅要考虑数据的完整性和一致性,还要考虑查询的性能。在设计关系表时应遵循以下原则:

(1) 尽量避免使用过多的关联查询,可以通过冗余数据来提高查询性能。

(2) 合理选择索引,以加快查询速度。索引应该选择那些经常被查询或用于连接的字段。

(3) 避免使用过多的字段,只选择必要的字段。

· 可扩展性原则

关系表设计应该考虑到系统的可扩展性,以便在将来的需求变化中能够方便地进行扩展。在设计关系表时应遵循以下原则:

  1. 尽量避免使用硬编码的字段,使用动态字段或扩展表来适应未来的需求变化。

(2) 尽量避免使用过多的嵌套关系,以便于后续的扩展和修改。

· 识别与正确处理多对多的关系

若两个实体之间存在多对多的关系,则应消除这种关系。消除的办法是,在两者之间增加中间表。中间表是业务逻辑中的概念,就是将计算结果先保存在一个临时的表中,然后再从这个表中计算,减少程序的复杂度。中间表包含两个实体类的主键作为外键,建立两张表关联关系。中间表的主键通常是采用两个实体表的外键作为联合主键,如果联合主键无法唯一确定记录,可以增加其他字段。

· E—R图设计原则

信息系统的E—R图没有标准原则,因为它的设计与画法不是惟一的,只要它覆盖了系统需求的业务范围和功能内容,就是可行的。反之要修改E—R图。尽管它没有惟一的标准答案,并不意味着可以随意设计。好的E—R图的标准是:结构清晰、关联简洁、实体个数适中、属性分配合理、没有低级冗余。

· 防止数据库设计打补丁的方法是“三少一多原则”

(1)一个数据库中表的个数越少越好。只有表的个数少了,才能说明系统的E—R图少而精,去掉了重复的多余的实体,形成了对客观世界的高度抽象,进行了系统的数据集成,防止了打补丁式的设计;

(2)一个表中组合主键的字段个数越少越好。因为主键的作用,一是建主键索引,二是做为子表的外键,所以组合主键的字段个数少了,不仅节省了运行时间,而且节省了索引存储空间;

(3)一个表中的字段个数越少越好。字段个数越多,数据冗余的可能性越大。设置字段个数少的前提是各个字段相互独立,而不是某个字段的取值可以由其他字段计算出来。当然字段个数少是相对的,我们通常会在数据冗余和检索效率中进行平衡。

(4)使用主键和外键越多越好。数据库的设计实际上就是定义各种表,以及各种字段之间的关系。这些关系越多,证明这些实体之间的冗余度越低,利用度越高。这样做的好处在于不仅保证了数据表之间的独立性,还能提升相互之间的关联使用率。

四、SQL性能优化

4.1 SQL优化调整的方向

· 去掉不必要的大型表的全表扫描;

· 缓存小型表的全表扫描;

· 检验优化索引的使用;

· 检验优化的连接技术;

· 尽可能减少执行计划的Cost。

4.2 优化的查询语句

绝大多数情况下,使用索引可以提高查询的速度,但如果SQL语句使用不恰当的话,索引将无法发挥它应有的作用。下面是应该注意的几个方面:

· 最好是在相同类型的字段间进行比较的操作。

· 在建有索引的字段上尽量不要使用函数进行操作。

· 在搜索字符型字段时,尽量少用或者避免使用like模糊查询,会降低查询效率,可以用其它条件查询语句代替。

· 应该注意避免在查询中让MySQL进行自动类型转换,因为转换过程也会使索引变得不起作用。

4.3 创建索引来优化性能

索引是提高数据库性能的常用方法,它可以令数据库服务器以比没有索引快得多的速度检索特定的行,尤其是在查询语句当中包含有MAX(), MIN()和ORDER BY这些命令的时候,性能提高更为明显。

一般说来,索引应建立在那些将用于JOIN, WHERE判断和ORDER BY排序的字段上。尽量不要对数据库中某个含有大量重复值的字段建立索引。

4.4 用Where子句替换HAVING子句

使用where子句替换Having子句:避免使用having子句,having只会在检索出所有记录之后才会对结果集进行过滤,这个处理需要排序分组,如果能通过where子句提前过滤查询的数目,就可以减少这方面的开销。

低效率:

|

SELECT JOB, AVG(SAL) FROM EMP GROUP BY JOB HAVING JOB = ‘PRESIDENT’ OR JOB = ‘MANAGER’

|
| |

高效率:

|

SELECT JOB, AVG(SAL) FROM EMP WHERE JOB = ‘PRESIDENT’ OR JOB = ‘MANAGER’ GROUP BY JOB

|
| |

4.5 通过内部函数提高SQL效率

复杂的SQL往往牺牲了执行效率. 能够掌握运用函数解决问题的方法在实际工作中是非常有意义的。

4.6 用表的别名(Alias)

当在SQL语句中连接多个表时, 请使用表的别名并把别名前缀用于每个Column上.这样一来,就可以减少解析的时间并减少那些由Column歧义引起的语法错误。

4.7 用EXISTS替代IN、用NOT EXISTS替代NOT IN

在许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接。在这种情况下,使用EXISTS(或NOT EXISTS)通常将提高查询的效率。在子查询中,NOT IN子句将执行一个内部的排序和合并。无论在哪种情况下,NOT IN都是最低效的 (因为它对子查询中的表执行了一个全表遍历)。为了避免使用NOT IN,我们可以把它改写成外连接(Outer Joins)或NOT EXISTS。

高效率:

|

SELECT 字段1,字段2,字段n FROM emp e WHERE e.emp_no > 0 AND EXISTS (SELECT ‘X’ FROM dept d WHERE e.emp_no = d.emp_no AND loc=’MELB’)

|
| |

低效率:

|

SELECT 字段1, 字段2, 字段n FROM emp e WHERE e.emp_no > 0 AND dept_no IN (SELECT dept_no FROM dept WHERE loc= ‘MELB’)

|
| |

4.8 用EXISTS替换DISTINCT

当提交一个包含一对多表信息(比如部门表和雇员表)的查询时,避免在SELECT子句中使用DISTINCT。一般可以考虑用EXIST替换,EXISTS使查询更为迅速,因为RDBMS核心模块将在子查询的条件一旦满足后,立刻返回结果。

低效率:

|

SELECT DISTINCT dept_no, dept_name FROM dept d, emp e WHERE d.dept_no = e.dept_no

|
| |

高效率:

|

SELECT dept_no, dept_name FROM dept d WHERE EXISTS ( SELECT ‘X’ FROM emp e WHERE e.dept_no = d.dept_no)

|
| |

4.9 避免在索引列上使用NOT

在索引列上使用NOT会停止使用索引转而执行全表扫描。

4.10 避免在索引列上使用计算

WHERE子句中,如果索引列是函数的一部分,优化器将不使用索引而使用全表扫描。

低效率:

|

SELECT 字段1, 字段2, 字段n FROM dept WHERE sal * 12 > 25000;

|
| |

高效率:

|

SELECT 字段1,字段2,字段n FROM dept WHERE sal > 25000/12;

|
| |

4.11 用>=替代>

高效率:

|

SELECT 字段1,字段2,字段n FROM emp WHERE dept_no >=4

|
| |

低效率:

|

SELECT 字段1,字段2,字段n FROM emp WHERE dept_no >3

|
| |

两者的区别在于, 前者DBMS将直接跳到第一个DEPT等于4的记录而后者将首先定位到DEPTNO=3的记录并且向前扫描到第一个DEPT大于3的记录。

4.12 用UNION替换OR (适用于索引列)

通常情况下, 用UNION替换WHERE子句中的OR将会起到较好的效果。对索引列使用OR将造成全表扫描。注意,以上规则只针对多个索引列有效。如果有column没有被索引,查询效率可能会因为你没有选择OR而降低。在下面的例子中,LOC_ID和REGION上都建有索引。

高效:

|

SELECT loc_id, loc_desc, region FROM loc WHERE loc_id = 10 UNION SELECT loc_id, loc_desc, region FROM loc WHERE region= ‘MELBOURNE’

|
| |

低效:

|

SELECT loc_id, loc_desc, region FROM loc WHERE loc_id = 10 OR region= ‘MELBOURNE’

|
| |

如果你坚持要用OR,那就需要返回记录最少的索引列写在最前面。

4.13 避免在索引列上使用IS NULL和IS NOT NULL

避免在索引中使用任何可以为空的列。对于单列索引,如果列包含空值,索引中将不存在此记录. 对于复合索引,如果每个列都为空,索引中同样不存在此记录. 如果至少有一个列不为空,则记录存在于索引中。

低效: (索引失效)

|

SELECT字段1, 字段2, 字段n FROM dept WHERE dept_code IS NOT NULL;

|
| |

高效: (索引有效)

|

SELECT字段1,字段2,字段n FROM dept WHERE dept_code >=0;

|
| |

4.14 使用连接(JOIN)来代替子查询(Sub-Queries)

连接(JOIN).. 之所以更有效率一些,是因为 MySQL不需要在内存中创建临时表来完成这个逻辑上的需要两个步骤的查询工作。

这是一段含有子查询的语句,为了提高效率可以用join代替

|

SELECT customer_id,customer_name FROM customer_info WHERE customer_id NOT IN (SELECT customer_id FROM sale_info)

|
| |

用join代替后的语句:

|

SELECT customer_id,customer_name FROM customer_info ci LEFT JOIN sale_info si ON ci.customer_id = si.customer_id WHERE si.customer_id IS NULL)

|
| |

4.15 大表进行拆分来提高性能

分析每个表可能的数据增长量,定义自动拆分表规则。将大表进行拆分来提高性能。

4.16 删除旧数据

预先考虑数据清理规则:在什么情况下删除数据库中的旧数据,以此来提高性能。

4.17 制定数据库备份和灾难恢复计划

制定定期备份数据库计划。

4.18 尽量把字段设置为NOT NULL

另外一个提高效率的方法是在可能的情况下,应该尽量把字段设置为NOT NULL,这样在将来执行查询的时候,数据库不用去比较NULL值。

4.19 使用联合(UNION)来代替手动创建的临时表

用 UNION 来创建查询的时候,我们只需要用 UNION作为关键字把多个 SELECT 语句连接起来就可以了,要注意的是所有 SELECT 语句中的字段数目要想同。下面的例子就演示了一个使用 UNION的查询。

如:

| SELECT name, phone FROM client UNION SELECT name, birthdate FROM author UNION SELECT name, supplier FROM product | | —- |

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容