程序员应该关注的数据库设计与编程规范

创建时间:2014/7/31 2:45:13
关键字:数据库设计

从多年的工作经历以及行业内一些朋友工作中,笔者了解到一些现状:国内软件行业内估计不在少数的企业,可能由于成本、管理规范等多方面的原因,没有专门负责数据库设计和编程的岗位。于是这方面的工作自然的由开发人员来主导:至于是由技术经理、专门指定的专业人员还是很多开发人员一起参与,就与每个公司的具体情况相关了。而该过程对设计和编程工作管理的规范程度,将在很大程度上影响到数据库结构的优劣、代码质量和执行效率、以及可扩展性和可维护性。

做为一名多年战斗在一线的开发者,笔者也较多地参与了数据库的设计和编程的工作。从事软件行业多年 的时间里,一直都伴随着数据库相关工作,可谓苦与乐共存。本文是多年心得的一个整理,不求全求备,只结合个人经历分享一些笔者认为值得关注的内容和观点。全面的知识点不是本文重点,本文只对开发人员常用到的部分进行讨论。

本文主要涉及的以下几个方面:

1. 数据库命名规范;

2. 数据库设计范式;

3. 数据库编程规范;

4.数据库设计和编程规范管理。

一、数据库命名规范

本来不想,还是提一下:如果您还在使用中文命名,我只能说,请不要在编程中使用火星语;对于使用纯数字如:111,或者没有任何含义的字母组合如:aaa,abc这类令人抓狂的命名方式,在项目中也零容忍的。

1.1命名规范解析

命名规范主要考虑如下几个方面:大小写、前后缀、组成部分、语义等的规范性。

大小写:根据特定情况使用Pascal 或CamelCase命名法,请不要使用全部大写或全部小写,这会使您的同事在查看您的项目或阅读您编写的代码时异常的痛苦。

前后缀:函数、存储过程等名称常用,多用于描述所代表对象的性质、功能等,如存储过程名称的sp_ ,up_,_Select,_Delete等。

组成部分:相对复杂的名称可由多个基本部分组成,要求每部分都遵循命名规范,部分的组成和顺序要合理、便于理解。如up_User_DeleteById 表示一个用户自定义存储过程,该存储过程针对User表进行操作的,具体功能是根据Id删除相应的记录。

语义:语义规范主要是要求命名能见文之义,每部分用准确的单词表示,尽量不用缩写,如果缩写,也要保证名称的可读性。不要使用字符的无意义组合。如表示产品名称使用ProductName,而不要使用PName、PdtName或ProdName等。

1.2 命名方式的选择

Pascal 命名法:第一个单字首字母采用大写字母;后续单字的首字母亦用大写字母。单字之间不以空格断开或连接号(-)、底线(_)连结。

CamelCase命名法:首字母小写,其他同Pascal命名法。

组合命名:由多部分组成,每部分遵循Pascal或CamelCase大大小写规范和及语义规范要求。

1.3 数据库命名规范

这里数据库以Ms Sqlserver进行举例,其整体思想与C#中面向对象思想是一致的。如果将数据库、数据表、表中的字段分别与C#中类库、类、类的属性相对应,就很容易理解对数据库、数据表、表中的字段采用Pascal命名法的合理性。如果您使用过代码生成技术为数据库生成实体层、数据操作层、业务层的代码,或者使用过Entity Framework、NHibernate等ORM技术,您会体会到到这样规范的命名的好处。

1.3.1 数据库命名

Pascal命名法:数据库库名,使用Pascal规则命名,即每部分单词首字符要大写。如:可用BookShop定义“图书商城”的库名。

组合命名:如果项目中涉及多个库,或者为便于管理不同项目的数据库,也可通过添加前缀采用组合命名方式。如用Children_BookShop定义“少儿图书商城”的库名,用Fashion_BookShop定义“时尚图书商城”的库名。组合命名的每部分也要遵循Pascal命名法规则。

例外:除非是IMS、CMS这样为大家所熟知的专业名词,对普通的单词不要使用全部大写。大写字符不符合人们的阅读习惯,会大大增加阅读和理解的难度。通常您需要将这些大写字符在脑海中“翻译”成小写,才会发现这一串陌生的字符原来是一个熟悉的单词。如:可用StudentIMS命名“学生信息管理系统”,LibraryIMS命名“图书信息管理系统”。

1.3.2 数据表命名

Pascal命名法:使用Pascal规则命名,就像定义C#中的类名一样。表名一般不要加前缀(tb_也不要加,实在没有必要),尽量不要使用下划线。如用Book命名图书表,用Price命名价格表等。

组合命名:多表间的关系表可使用下划线连接,Re_Book_Author或R_Book_Author命名“图书与作者的关系表”,Re是Relative的缩写。

前缀例外:对于大型系统,有多个子系统(或多个模块),数据表非常多的情况,可按子系统(模块)缩写前缀 实际表名命名,二者间使用下划线连接。如:系统管理模块表使用Sys_开头,。

网上查过很多关于数据库命名的介绍,总说纷纭,让人莫衷一是。以下这篇文章比较难得,对其观点与笔者契合度达到90%以上,因此链接在此做个推荐。

http://www.360doc.com/content/12/0823/09/9318309_231847691.shtml

1.3.3 字段命名

Pascal命名法:不要使用小写开头的CamelCase命名法,字段名不要加表名前缀。当您使用代码生成技术为表生成实体类时,这些字段的名称会被应用到实体的属性上。想像一下,如果属性以小写开头,或者以类名前缀开头(当字段以表名加下划线开头时),该有多糟糕!

弃用ID:使用“Id”不要使用“ID”。这里“Id”完全遵循了Pascal规则,也是现在推荐的用法,“ID”则是历史遗留的不规范的用法。在一个公司里不同的开发人员协同设计数据库时,出现Id和ID同事存在的情况。这种情况会相应的存在实体类中。在web开发,数据源转为Json格式数据后,前端页面取属性时可能因为Id的大小写弄错而导致前端页面的异常。

语义规范:如推荐使用”UserName”,而不是”Uname”。使用”Password“而不是”Pwd”。很多时候,字段的缩写不同程度的降低可读性,当多人协同设计时,因各自缩写风格不一致,该问题会更加突出。

1.3.4 存储过程命名

CamelCase命名法:使用首字母小写的驼峰命名法。命名构成及说明如下:

[proc][MainTableName] By [FieldName(optional)] [Action]

存储过程必须有proc、up等前缀,前缀选择由公司具体情况而定。

表名就是存储过程访问的对象;

条件部分:可选字段名就是条件子句;

最后的行为动词就是存储过程要执行的任务,Select、Insert、Update、Delete、Save(有更新和插入)、Output(返回输出参数或0)。

如:procUsersByNameGet、upUsersByNameGet。

组合命名:有下划线连接的多部分组合而成。各部分组成及说明如下:

  up_[MainTableName]_[Action]By[FieldName(condition)]

构成说明同上。

up_Users_Get、up_Users_GetByName、up_Users_Delete

关于sp_:

推荐不使用这样的前缀,因为会稍微的减慢。任何的系统存储过程都有前缀"sp_",即使当前数据库中存在带 sp_ 前缀的用户自定义的存储过程,总会先检查 master 数据库。

1.3.5 函数命名

格式:fn_Name 或func_Name,建议根据企业实际情况确定统一的前缀。 

前缀用来标志这是一个函数,用以与系统函数相区别。Name则根据函数功能进行命名,一个或多个单词都可,Name部分遵循Pascal命名法。

有见到函数名所有字符大写的情况,从规范性和可读性角度考虑,不赞同这种命名习惯。

二、数据库设计规范

这里讨论的关系型数据库的设计应遵循的基本规范,NoSql不在本次讨论范围内。

2.1 数据库设计三大范式

2.1.1 列的原子性

 第一范式(1NF),即每个属性都是不可再分的最小数据单元(不能是多个单元的组合)。

例如:假定一件物品的描述,包含尺寸和颜色两个方面的信息。这时应该做为分开的两个属性来定义,虽然很多情况下二者的组合最终来确定这件物品的型号。

2.1.2 主键依赖

表内的每一行数据都应该被唯一标志(有唯一键)。

满足第二范式(2NF)必须先满足第一范式(1NF)。

2.1.3 消除传递依赖

表内不应该存在依赖于其他键(非主键)的非键属性,所有非主键属性应该只依赖于主键。简而言之,第二范式就是非主属性非部分依赖于主关键字。

满足第三范式(3NF)必须先满足第二范式(2NF)。

2.2 违反三大范式的情况

也许在一些开发者朋友看来,遵守三大范式实在不是什么事,不值得大费笔墨来讨论。

但是我之所以在这里讨论,是因为曾经确有遇到这种情况:有的是非开发部人员(如产品)因为不懂数据库的设计,给一些不那么靠谱的建议也是难免的。也有一些开发人员,可能工作中没接受规范培训,或者是不太重视,常常在有意无意间“”。

2.2.1 第一范式的失守

第一范式,应该是也很容易理解,也很容易做到的,在建表时确保字段代表的属性不可拆分就行了。但有时事实未必令人如此乐观的。

举个例子,有一张用户信息表,已有联系电话字段(命名为Telephone),存储的是用户办公电话号码。可能有一天,开发部接到一个紧急的需求:因为有时需要在下班时间联系用户,因此需要存储用户的家庭电话号码,而且时间很紧急,希望马上改出来。我相信一定存在一些极其敏捷的人,他们能提出类似这样的方案:页面上加一个文本框用来输入家庭号码,存储时与办公电话号码用分好(;)或竖线(|)拼接起来,存到Telephone这一列,查询的时候Split处理一下就好了。我相信真的会有人这么干,为什么?数据库,数据访问层,业务层全都不用变了,只要改改界面,几分钟就好,“省事”,“高效”。于是,第一范式在这里失守了!

其实,上面这个例子,应该说数据表的设计者可能有考虑不周全因素,设计的字段不够。但问题的发生却是后期由于相关人员没有遵守规范,对表中字段的滥用造成的。修改时应该对原有的设计进行完善(如增加字段),而不是进行破坏性的改动。

2.2.2 被遗忘的第二范式

建表不设主键,这是造成违反第二范式的直接原因。情况可能有两种:

1. 建表时有设计Id,或有可以做为主键用的字段,但是没有给表设主键。这种情况有可能是设计者无意忘记了,也有可能是习惯性的做法。通常不会产生什么严重的问题,有问题也能及时发现如表中出现非预期的重复性数据。但还是会有别的问题,这个后面会讨论。

2. 建表时没有专门设计可以用作主键的字段,也没有任何字段符合做为主键用的条件。这种情况有可能会造成很麻烦的问题。

以上两种情况是本人在一些项目中遇到过的,是确有其事的。

2.2.3 被忽视的第三范式

前面两种情况设计上的不规范问题,一般提出来都会被接受,因为能避免一些不必要的麻烦。相比前面两种情况,违反第三范式有时似乎直接被忽视。一些设计者或开发人员为了查询数据的便利,直接忽视该范式的存在,坚持要保留一些冗余字段。

比如一张业务表中已经有城市表的主键字段(CityId),非要再定义一个城市名称字段(CityName),虽然在City表中可以通过CityId确定CityName了。因为这样在查询的时候,就不用join了,能直接从业务表查出想要的城市名称。

由于他们的“据理力争”和相关人员的妥协,这种不合理的设计可能会长期沿用而得不到修正,而新的冗余字段又在不断产生。在这里,第三范式就形同虚设了。

2.3 数据库三大范式的重要性

而关系型数据库设计初衷就是为了是数据存储更结构化、更有序。只要这样才能支持高效的查询和灵活的扩展,也只有这样才能支撑复杂的应用软件系统。

满足设计范式的数据库是简洁的、结构明晰的,不会发生插入、删除和更新产生操作异常。反之则是混乱的,不仅给数据库的编程人员制造麻烦,而且可能存储了大量不需要的冗余信息。

2.3.1关于第一范式

第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。

在一个字段中通过分隔符拼接多项属性的做法,是一种极其不负责任的偷懒的做法。当数据库中这种不规范的情况不断增多,系统业务逐渐复杂,工程代码量不断增大,开发人员不断更替,系统后期的维护和扩展将会越来越困难,而系统业务越复杂,这种弊端也就越明显。而当系统长时间运行后,即便想要修正这些问题,也会投鼠忌器了。

2.3.2 第二范式的作用

对于没有主键的表,其他表无法将其做为子表的使用。

按条件分页查询,跳过多少行时通常需要用到主键,没有主键将会很麻烦。

ORM或都会需要用到主键,如果有的表没有主键,可能影响方案的应用。而没有主键可能导致使用代码生成技术批量生成底层基础代码时异常。

2.3.3 关于第三范式

与第三范式背道而驰会导致数据库中产生大量的冗余数据,同时也让人编程的时候感到无所适从。

如上面的例子中,假设City表中某个城市的名称发生变化了,要在业务表中去更新所有的业务记录中的城市名称,否则将会导致同一个城市在系统中不同地方的名称不一致。而这增加的是完全不必要的工作。另外当需要按城市条件筛选数据时,有的人可能用CityId比较,有的可能会用CityName去比较,会造成一些混乱。如果不幸用的CityName,碰巧城市表中某个城市的名称改变了,如果业务表没有即时更新过来,就会导致某些功能这里的筛选失效。这实际上也是增加了整个系统的耦合性,让系统变得更脆弱了。

三、数据库编程规范

3.1 存储过程的使用

3.1.1存储过程特点

优点:

经过预编译,查询快。

存储过程中可定义变量、临时表,可使用游标等,处理复杂查询及对查询进行二次处理。

对于C/S架构系统,方便扩展和修改业务,特别避免了客户端重新编译和更新的麻烦。

缺点:

可移植性差,从sql server换成mysql或Oracle,移植处理存储过程麻烦。

降低程序的可读性、增加调试的难度。

3.1.2存储过程使用原则

通常的情况,简单的增删查改不要使用存储过程。过多使用存储过程降低了程序可读性,也增加维护存储过程的工作。

没有特殊的必要,不要将过多的业务逻辑全部放到一个存储过程里面(使用存储过程处理复杂的查询而不是处理大量的业务逻辑和判断)。

3.1.3存储过程使用场景

批量查询:在程序中的需多次调用方法,多次连接查询,在存储过程只要一次连接,实现批量查询。

分页查询:方便处理多条件、多表联合查询,并做分页处理的操作。

复杂查询:存储过程中可以方便的使用游标、临时表等来处理比较复杂的统计和汇总。普通的Sql语句难以实现复杂的查询,Sql语句写复杂了难以理解和维护。

C/S架构缓解网络带宽:对于批量执行SQL语句,而从客户端发送存储过程名称和参数请求,不用发送所有的Sql语句。

3.2 触发器的使用

触发器对并发性能影响太大,Web开发中一般不使用触发器。

C/S架构的特殊项目有使用触发器的情况。不过一般情况能不用就不用。

3.3 外键关联的使用

 使用了外键关联,删除和修改是都必须考虑外键约束,给开发增加麻烦。

 一些业务复杂的企业级系统中有用外键关联保证数据的完整和一致。

 从性能角度考虑,没有特殊的必要,Web开发中一般不建议使用外键关联。

四、数据库设计与编程规范管理

4.1 级别限制

有专门的人员负责数据库的设计和编程工作,严格从源头保证数据库结构和代码的纯净和规范。

问题:中小企业人手不够,通常开发人员会同时负责各个环节,较难落实专人负责制度。

4.2 审核机制

放宽参与设计人员的限制,但是所有参与者的设计要通过负责人员的审核,才能生效投入使用。

4.3 培训与规范要求

如果以上两点都无法得到较好地落实,至少应该对参与设计的人员进行必要的培训,要求大家自觉遵守规范。

公司制定统一的规范,应强制要求员工遵守,不可随意违反规范进行设计。员工对规范有意见或建议,可以提出,公司可采纳合理的建议对规范进行修订。

      (全文完)

(转载本文请注明作者和出处 i 编程网-iprogram.com.cn ,请勿用于任何商业用途)