不同的关系型数据库在字段类型的具体化上差异较多,这里无法一一详述,但具体化的字段类型再多,无外乎几种:字符、数字、日期、二进制。
表中应该尽可能避免可为NULL的列,且尽可能显示设置默认值,尤其是被索引的列。
为什么?
在MySQL数据库中,空值是不占用空间的,而NULL其实是占用空间的。再者,MySQL表的列中包含NULL的话,该列就不会包含在索引中,也就是说使用索引是无效的,现在不确定其它数据库是否也是如此。所以考虑今后可能会使用索引的字段,就要设置字段属性是NOT NULL。比如,如果某个字段后面可能会作为查询关键字使用LIKE的形式进行搜索,就要将该字段定义成索引以提高查询速度,那这个字段属性就是NOT NULL的。
除以下数据类型的字段外:timestamp、image、datetime、smalldatetime、uniqueidentifier、binary、sql_variant、binary、varbinary,表字段应尽可能显示设置默认值。建议数值型的默认值为数值0,布尔型的默认值为数值1(通常情况下,系统中所有逻辑型中数值0表示为“真”、“正常的”;数值1表示为“假”、“异常的”,这种编码后面还会有介绍),datetime、smalldatetime类型的字段没有默认值,必须为NULL。
如果数据库中某个字段有默认值,那么觉得在程序开发过程中,对应实体类的属性应该设置同样的初始化值才合理,记得动软代码生成工具中的框架就是这样设置。之前自己的程序设计中没有注意到这点,自动生成的所有实体类的属性都没有默认值。
注意区分NULL和空字符串是不同的,数值型字段中NULL和0更是两码事。如果在数据库设计过程中不允许出现NULL字段还好,但如果有允许NULL而没有设置默认值的字符型字段,程序对记录执行了写空字符串动作和压根未执行写动作是两码事;如果有允许NULL且设置默认值为空字符串的字段,则无法做这种区分。当然,通常情况下,我们认为文本框中空字符串的提交动作等同于未执行写入。有些类似的情况是,在程序开发中,一个空的List对象,或者一个new出来的空对象,和NULL也是不同的,要注意。
像订单(会诊单)这种表,取消、退回、安排这些字段的信息都不是必须有的,可以分流到子表中存储,放在一张表中会导致出现很多可为NULL或空值的列。之前并不赞成这种过分分流的方法,因为这会另信息的维护变得麻烦,如再有类似情况,应该根据实际综合判断取舍。也要在设计时尽可能遵守第二、三范式,非主属性完全依赖于码(主键)、消除传递依赖,不要让某张表过分臃肿。
当字段定义为字符串类型时建议使用varchar而不用nvarchar以节省空间,通常情况下,都要用尽量少的存储空间来存储一个字段的数据,能用int类型的就不用char类型,能用char类型就不用varchar类型,能用varchar(20)的就不用varchar(25)。char和varchar长度设计需要根据业务实际需要进行长度控制,禁止预留过长空间。比如主键要求用UUID,那就统一为char(32),可以固定的部分就都固定下来。varchar类型虽然根据实际长度进行存储,但内存分配则是根据指定长度,不合理的长度设计会导致内存的不合理占用。
varchar是变长存储,字段长度是数据库一种约束,定义合理的长度也可以让人容易理解字段的用途。MySQL中定义的长度如果小于255,字段长度用1个字节表示,如果超过255,字段的长度将固定用2个字节表示。Oracle没有这样的问题。字段定义的长度对索引也有较大影响,MySQL数据库索引存储的长度都是定义的长度,不是实际字符的长度,这是一个非常大的问题,估计主要原因是为了实现简单,所以MySQL在索引上会浪费大量的空间保存字符串。
前台、程序以及数据库各部分之间对字段大小的限制务必处理恰当,为了节省存储空间,选取的数据库字段容量在一定范围内应该尽可能小,而为了对程序提供更好的扩展支持,又应该尽可能的设置大些,具体字段类型、字段长度如何设置,根据实际情况取得均衡。而后台程序部分,对数值大小长度应该做好校验处理,确保插入数据库的值大小长度不要超过限制。同时前端也应该给出明确的校验提醒,让用户按提示输入,决不允许不提醒用户而擅自把数据处理后插入数据库中(这种错误真有人犯过)。这样,前端、程序、数据库全方位校验处理,自然可以保证数值的准备性、存取的合理性。
除非要保存文章内容,text字段尽量少用,如果要用能拆到冗余表中最好。禁止使用blob类型保存大文本、附件、图片等,对于图片、文档等附件数据库中只保留原始文件名和存储路径。网上也有建议使用其他存储方式的,比如TFS、SFS等,可以参考。
禁止使用float、double类型,建议使用decimal替代。decimal(a,b),a指定指定小数点左边和右边可以存储的十进制数字的最大个数,最大精度38。b指定小数点右边可以存储的十进制数字的最大个数。小数位数必须是从0到a之间的值。默认小数位数是0。比如decimal(5,2)规定了存储的值将不会超过5位数字,并且小数点后面有2位数字。
在Oracle中,CHAR为定长字符串,最长2000字节。VARCHAR2为变长字符串,最长4000字节。NCHAR和NVARCHAR2分别与CHAR和VARCHAR2相对应,但存储的数据为NLS字符。
目前VARCHAR是VARCHAR2的同义词。工业标准的VARCHAR类型可以存储空字符串,但是Oracle不这样做,尽管它保留以后这样做的权利。Oracle自己开发了一个数据类型VARCHAR2,这个类型不是一个标准的VARCHAR,它将数据库中VARCHAR类型的列可以存储空字符串的特性改为存储NULL值。如果你想有向后兼容的能力,Oracle建议使用VARCHAR2而不是VARCHAR。
在Oracle中没有TEXT类型,但有用于大文本存储的CLOB类型。Clob是指大字符对象,也就是英文Character Large Object的缩写;Blob是指二进制大对象,也就是英文Binary Large Object的缩写;由此可见这两个类型都是用来存储大量数据而设计的。
LONG最大存储2G字符数据,但现在已不推荐使用(改用CLOB);CLOB在Oracle 9i及以前,最大可存储4G字符数据,在Oracle10g及以后,最大可存储4G*数据库块大小的字符数据;NCLOB基本同CLOB,就是存储的数据为NLS。
在Oracle数据库表中使用CLOB类型字段,最大的问题是备份数据时不好处理。在有些情况下,给政府、企业做项目,只给你Oracle的访问权限,而不给你Oracle所在服务器的操作权限,也就是说自己无法操作Oracle服务端工具。但Oracle的客户端中又没有exp、expdp命令,这样备份导出数据库就不好弄了(此处不提沟通协调甲方处理)。SQL Developer是Oracle的官方工具,用其导出数据库,如果导出的是SQL格式,那CLOB类型字段的数据将直接被忽略——这绝对是无法接受的。官网上有文章说可以将数据库导出为loader或pdf格式,自己尝试导出这两种格式,发现不能导出成单个文件,会导出很多的文件。而且导入时也需要用到额外的工具——Oracle服务端的sqlldr.exe,这样只借助客户端也是不行的。
在SQL Developer“工具”菜单下,还有两个选项:“数据库Diff”及“数据库复制”,如果所处网络可同时访问源数据库和目标数据库,可用这种方法互相拷贝数据,但是同样的问题,这种数据库复制方法,仍然是不能处理COLB、BLOB的字段。而且我发现,凡是带有这两种字段的表,在复制时都没有数据,不是相应字段没有数据、是整个表的数据都没有复制,其它没有BLOB、CLOB字段的表,数据拷贝都正常。
也曾想使用的PL/SQL Developer工具进行备份,导出了PL/SQL Developer自己的格式(pde)。可是却提示stream read error,到网上一查,原来PL/SQL Developer自己的格式也是不支持COLB、BLOB类型字段的导出的。
之前同事介绍过Navicat for Oralce工具,但其在导出CLOB、BLOB类型的字段时,如果字段中的数据过长,也是无法再正常导入的。这个小工具看似简单轻巧,在执行一些操作时问题却很多,不宜作为一款常用的Oracle管理工具。
这样看来,只有expdp命令才能有效导出clob、blob格式的字段了。
不过,如果你虽然没有源数据库服务端的访问权限,却有目标数据库服务端的访问权限,且两个库可在一个网络中访问,也是有办法用EXP命令备份源数据库的。就是让目标数据库服务端的TNS监听源数据库的实例,再利用目标数据库Oracle服务端的exp.exe工具远程导出源数据库,导出导入命令和上面类似:
此外,用PL/SQL Developer工具备份数据时,Export User Objects菜单命令导出的是SQL文件,在这里你可以将建表、序列、触发器、存储过程等的SQL语句全部导出成一个文件,但是这里面并不包括数据。要想导出数据,必须用Export Tables……菜单命令,导出DMP文件。当然也可以导出其它格式的文件(SQL、PDE),但建议用DMP格式,因为前面已经说过,如果表中有CLOB类型字段的话,用其它格式的导出方式恐怕有问题。
如果一个表不存在,而这个表中没有CLOB、BLOB这种特殊数据类型的字段,用DMP导入数据时PL/SQL会自动建立这个表。但如果一个表不存在,而这个表中又有CLOB、BLOB这种特殊字段,直接导入DMP格式的文件会报错IMP-00003:遇到ORACLE错误959。所以在Oracle中导入数据库时应该先执行用Export User Objects导出的SQL文件,这样相关的序列、触发器、表结构都已经建好了,再导入用Export Tables……导出的DMP文件,也就是导入其中的数据,就万全了。
float:浮点型,含字节数为4,32bit,数值范围为-3.4E38~3.4E38(7个有效位)
double:双精度实型,含字节数为8,64bit数值范围-1.7E308~1.7E308(15个有效位)
decimal:数字型,128bit,不存在精度损失,常用于银行帐目计算。(28个有效位)
float和double的相乘操作,数字溢出不会报错,会有精度的损失。当对decimal类型进行操作时,数值会因溢出而且报错。
Oracle中的数值类型,Oracle只是在语法上支持decimal类型,但是在底层实际上它就是NUMBER类型,支持decimal类型是为了能把数据从Oracle数据库移到其他数据库中(如MySQL、DB2等)。Oracle的NUMBER数据类型的精度:NUMBER(P,S),P:1—38,S:-84—127。S代表的是小数位数,P代表的是总位数(整数位数和小数位数)。所以,平时如果在Oracle中用自增主键,长度设为NUMBER(10)的话,相当于NUMBER(10,0),表示最高可记录到十亿级的数据量。
下图是MySQL中的整数型数值类型详述:
日期时间类型字段,网上有建议,采用int来记录unix_timestamp,自己还是习惯用datetime。不过设计原则是粒度越小越好,所以这里要求日期时间类型的字段,尽可能精确到时分秒,用datetime类型。即便是像生日(birth_date)这种字段,一般只存储到年月日,但在选择字段类型时建议还是用datetime而非date,以防万一。如有部分时间字段着实无须记录到时分秒,则用date类型。严禁使用varchar等字符串类型记录日期时间,更不要把时间猜分,年在单独的字段、月在单独的字段、日又是单独字段,老实讲TM想不明白这种人的设计思路是什么样的。
网络IP字段,网上有建议,除特殊情况一律用bigint来记录inet_aton值,但这种存储方式貌似只在MySQL中适用,这里要求还是用varchar存储。关于inet_aton想了解的话可以看下参考文献中的“MySQL的IP处理函数inet_aton()和inet_ntoa()”。
字典编码字段,之前在SQLServer中设计数据库时统一使用char(2)类型,Oracle数据库中统一使用number(2),在MySQL中统一使用tinyint(2)。现在想来最合理的还是设置为tinyint(2),以后数据库字典编码字段统一按此设置。就是Oracle中没有tinyint类型,不知道如果在PD中设置此种类型,导入到Oracle时会自动转换处理还是直接报错。
备注字段,尽可能在所有表中都保留这个字段,也是给前端信息录入预留一个可扩展部分。统一命名为remark,字段类型为varchar(200),最多100个中文字符。再多的话说明有额外信息,就不适合放在备注字段中了,要再加新字段存储。
排序字段,不是每个表中都需要额外的排序字段,但有些表这必须有,比如记录菜单信息的表、门户网站中存放文章内容的表等。这里推荐统一使用int(10)做为所有表中的排序字段类型。
字段设置部分撰述内容较多,相对详细,这是比较重要的一部分。以后的数据库设计,字段类型选择、字段长度设置部分都要以此为依据。