首頁  >  文章  >  資料庫  >  数据库设计指南之我见

数据库设计指南之我见

WBOY
WBOY原創
2016-06-07 15:24:171478瀏覽

网上流传着一份关于数据库设计的文档《数据库设计指南》收集了几十个数据库设计大牛在项目中总结出来的Best Practice最佳实践,我最近也花了点时间细读并结合自身实际进行了总结,感觉自己在项目中还是有不少不足的地方,下面逐条分析下。(黑字为原文,红字

网上流传着一份关于数据库设计的文档《数据库设计指南》收集了几十个数据库设计大牛在项目中总结出来的Best Practice最佳实践,我最近也花了点时间细读并结合自身实际进行了总结,感觉自己在项目中还是有不少不足的地方,下面逐条分析下。(黑字为原文,红字为我的见解)

数据库设计指南
如果把企业的数据比做生命所必需的血液,那么数据库的设计就是应用中最重要的一部分。有关数据
库设计的材料汗牛充栋,大学学位课程里也有专门的讲述。不过,就如我们反复强调的那样,再好的
老师也比不过经验的教诲。所以我们最近找了些对数据库设计颇有造诣的专业人士给大家传授一些设
计数据库的技巧和经验。我们的编辑从收到的130个反馈中精选了其中的60个最佳技巧,并把这些
技巧编写成了本文,为了方便索引其内容划分为5个部分:
第1部分—设计数据库之前
这一部分罗列了12个基本技巧,包括命名规范和明确业务需求等。
第2部分—设计数据库表
总共24个指南性技巧,涵盖表内字段设计以及应该避免的常见问题等。
第3部分—选择键
怎么选择键呢?这里有10个技巧专门涉及系统生成的主键的正确用法,还有何时以及如何索引字段
以获得最佳性能等。
第 4 部分 — 保证数据完整性
讨论如何保持数据库的清晰和健壮,如何把有害数据降低到最小程度。
第5部分—各种小技巧
不包括在以上4个部分中的其他技巧,五花八门,有 了它们希望你的数据库开发工作会更轻松一些。

第1部分—设计数据库之前
1. 考察现有环境
在设计一个新数据库时,你不但应该仔细研究业务需求而且还要考察现有的系统。大多数数据库
项目都不是从头开始建立的;通常,机构内总会存在用来满足特定需求的现有系统(可能没有实
现自动计算)。显然,现有系统并不完美,否则你就不必再建立新系统了。但是对旧系统的研究
可以让你发现一些可能会忽略的细微问题。一般来说,考察现有系统对你绝对有好处。
—Lamont Adams
我曾经接手过一个为地区运输公司开发的数据库项目,活不难,用的是Access数据库。我设置
了一些项目设计参数,而且同客户一道对这些参数进行了评估,事先还查看了开发环境下所采取
的工作模式,等到最后部署应用的时候,只见终端上出了几个提示符然后立马在我面前翘辫子
了!抓耳挠腮的折腾了好几个小时,我才意识到,原来这家公司的网络上跑着两个数据库应用,
而对网络的访问需要明确和严格的用户帐号及其访问权限。明白了这一点,问题迎刃而解:只需
采用客户的系统即可。这个项目给我的教训就是:记住,假如你在诸如Access或者Interbase 这
类公共环境下开发应用程序,一定要从表面下手深入系统内部搞清楚你面临的环境到底是怎么回
事。
—kg

考虑现有环境是必须的,我们做的好几个系统都是替换原来的就系统,所以必然涉及到历史数据的迁移,而这个工作也是由我们来完成,所以在设计数据库时就要考虑到历史数据库的结构,存放了哪些数据。在新系统数据库设计之前,需要对旧系统的数据库进行分析,如果有数据库设计文档就看文档,没有的话就需要用PowerDesigner逆向工程出模型图然后进行分析,旧系统的数据库Schema对新系统数据库的设计影响较大。

另外关于数据库类型倒没有考虑太多,旧系统是SQL Server,新系统是MySql也没有什么,毕竟我们是从概念模型开始进行设计,所以对数据库类型依赖不大。
2. 定义标准的对象命名规范
一定要定义数据库对象的命名规范 。对数据库表来说,从项目一开始就要确定表名是采用复数还
是单数形式。此外还要给表的别名定义简单规则(比方说,如果表名是一个单词,别名就取单词
的前4个字母;如果表名是两个单词,就各取两个单词的前两个字母组成4个字母长的别名;如
果表的名字由3个单词组成,你不妨从头两个单词中各取一个然后从最后一个单词中再取出两个
字母,结果还是组成4字母长的别名,其余依次类推)对工作用表来说,表名可以加上前缀
WORK_ 后面附上采用该表的应用程序的名字。表内的列要针对键采用一整套设计规则。比如,
如果键是数字类型,你可以用_NO作为后缀;如果是字符类型则可以采用 _CODE后缀。对列名
应该采用标准的前缀和后缀。再如,假如你的表里有好多“money”字段,你不妨给每个列增加
一个_AMT后缀。还有,日期列最好以DATE_作为名字打头。
—richard
检查表名、报表名和查询名之间的命名规范。你可能会很快就被这些不同的数据库要素的名称搞
糊涂了。假如你坚持统一地命名这些数据库的不同组成部分,至少你应该在这些对象名字的开头
用table、query或者report等前缀加以区别。
—rrydenm
如果采用了Microsoft Access,你可以用 qry、rpt、 tbl 和mod等符号来标识对象(比如
tbl_Employees)。我在和SQL Server(或者Oracle)打交道的时候还用过tbl 来索引表,但我
用sp_company (现在用sp_feft_)标识存储过程,因为在有的时候如果我发现了更好的处理办
法往往会保存好几个拷贝。我在实现 SQL Server 2000时用udf_ (或者类似的标记)标识我编
写的函数。
—Timothy J. Bruce
对象命名规范这个是必须的,在程序编写时如此,在数据库对象命名时更是如此,尤其是我们在项目中引入了ORMapping和FluentNhibernate的AutoMapping后,对命名要求更加严格,比如要求所有的数据库对象以大写字母拼写,单词之间用下划线分割,主键都是采用表名+“_ID”,视图都以V_开头,date类型以DATE结尾,datetime类型以TIME结尾等。
3. 预先计划
上个世纪80年代初,我还在使用资产帐目系统和System 38平台,那时我负责设计所有的日期
字段,这样在不费什么力气的情况下将来就可以轻松处理2000年问题了。许多人给我说就别去
解决这一问题了,因为要处理起来太麻烦了(这在世人皆知的Y2K问题之前很久了)。我回击说
只要预先计划今后就不会遇到*烦。结果我只用了两周的时间就把程序全部改完了。因为预先
计划的好,后来Y2K问题对该系统的危害降到了最低程度(最近听说该程序甚至到了1995年都
还运行在AS/400系统上,唯一出现的小问题是从代码中删除注释费了点工夫)。
—generalist

千年虫的问题现在肯定是遇不到了,肯定我们的系统运行不到下一个千年。但是关于预先计划这个在数据库设计时当然是要考虑的,比如考虑到用户可能会误操作,然后又要求维护人员恢复数据,需要对表进行软删除(逻辑删除,也就是增加一个IS_DELETED字段,0表示正常,删除操作就是将该字段设置为1)
4. 获取数据模式资源手册
正在寻求示例模式的人可以阅读《 数据模式资源手册 》一书,该书由Len Silverston、W. H.
Inmon和Kent Graziano编写,是一本值得拥有的最佳数据建模图书。该书包括的章节涵盖多种
数据领域,比如人员、机构和工作效能等。
—minstrelmike

这个值得推荐,这本书目前已经出了三卷,翻译成中文的有两卷,可惜这本书中文版都绝版了,只有买到复印版,我看的也是复印的。这里面介绍了各个领域的通用模型,非常值得参考学习。
5. 畅想未来,但不可忘了过去的教训
我发现询问用户如何看待未来需求变化非常有用。这样做可以达到两个目的:首先,你可以清楚
地了解应用设计在哪个地方应该更具灵活性以及如何避免性能瓶颈;其次,你知道发生事先没有
确定的需求变更时用户将和你一样感到吃惊。
—chrisdk
一定要记住过去的经验教训!我们开发人员还应该通过分享自己的体会和经验互相帮助。即使用
户认为他们再也不需要什么支持了,我们也应该对他们进行这方面的教育,我们都曾经面临过这
样的时刻“当初要是这么做了该多好……”。
—dhattrem

灵活性就意味着复杂性,越是灵活的系统,设计也就越复杂,实现起来也越麻烦,所以在灵活性上很有必要,但是也适可而止。在数据库设计时有个最常见的问题就是AB两个对象到底是一对一还是一对多,虽然现在是一对一,但是从长远来可能会出现一对多的情况,那么就需要考虑成一对多的设计。最麻烦的是一对多还是多对多的问题,因为多对多意味着需要建立中间表,为程序的编写,SQL脚本的编写带来较大的变化,所以如果考虑到未来可能是多对多的最好是先设计成多对多,要不然以后需求更改,代码改起来很麻烦。
6. 在物理实践之前进行逻辑设计
在深入物理设计之前要先进行逻辑设计。随着大量的 CASE工具不断涌现出来,你的设计也可以
达到相当高的逻辑水准,你通常可以从整体上更好地了解数据库设计所需要的方方面面。
—chardove

这个是必须的,由于我在项目中基本采用PowerDesigner的概念模型-》逻辑模型-》物理模型的流程,所以在设计时不用考虑具体数据库的实现,也更容易的设计和处理对象的继承,多对多引用等。
7. 了解你的业务
在你百分百地确定系统从客户角度满足其需求之前不要在你的ER(实体关系)模式中加入哪怕
一个数据表(怎么,你还没有模式?那请你参看技巧9)。了解你的企业业务可以在以后的开发
阶段节约大量的时间。一旦你明确了业务需求,你就可以自己做出许多决策 了。
—rangel
一旦你认为你已经明确 了业务内容,你最好同客户进行一次系统的交流。采用客户的术语并且向
他们解释你所想到的和你所听到的。同时还应该用可能、将会和必须等词汇表达出系统的关系基
数。这样你就可以让你的客户纠正你自己的理解然后做好下一步的ER设计。
—teburlew
我觉得这个说法不现实,软件开发本来就是一个渐进明细的过程,而且需求肯定是在不断变化的,所以数据库模型必然也跟着进行修改。我们能做的就是抓住数据库模型中的核心实体以及核心实体的关系,其实需求的变化主要是实体属性的变化和细节上关系的变化,基本的核心还是不变的。
8. 创建数据字典和ER图表
一定要花点时间创建ER图表和数据字典。其中至少应该包含每个字段的数据类型和在每个表内
的主外键。创建ER图表和数据字典确实有点费时但对其他开发人员要了解整个设计却是完全必
要的。越早创建越能有助于避免今后面临的可能混乱,从而可以让任何了解数据库的人都明确如
何从数据库中获得数据。
—bgumbert
有一份诸如ER图表等最新文档其重要性如何强调都不过分,这对表明表之间关系很有用,而数
据字典则说明了每个字段的用途以及任何可能存在的别名。对SQL表达式的文档化来说这是完
全必要的。
—vanduin.chris.cj

这个必须得有。一方面ER图表方便开发人员更直观的了解数据库的结构,另一方面也可以对上进行汇报。由于采用了PowerDesigner,所以这个工作变得非常简单,根据模型可以直接生成数据字典和ER图表,不需要再花时间在文档工作上,只需要把模型维护好即可。
9. 创建模式
一张图表胜过千言万语:开发人员不仅要阅读和实现它,而且还要用它来帮助自己和用户对话。
模式有助于提高协作效能,这样在先期的数据库设计中几乎不可能出现大的问题。模式不必弄的
很复杂;甚至可以简单到手写在一张纸上就可以了。只是要保证其上的逻辑关系今后能产生效
益。
—Dana Daigle

这个就用PowerDesigner实现即可。使用其Report功能生成Html,然后发布到服务器上便于开发人员查看,生成word文档便于向领导汇报。
10. 从输入输出下手
在定义数据库表和字段需求(输入)时,首先应检查现有的或者已经设计出的报表、查询和视图
(输出)以决定为了支持这些输出哪些是必要的表和字段。举个简单的例子:假如客户需要一个
报表按照邮政编码排序、分段和求和,你要保证其中包括了单独的邮政编码字段而不要把邮政编
码糅进地址字段里。
—peter.marshall

这个可以认为是一种需求分析的方法,知道用户会输入什么,系统要输出什么才能知道设计对于的表字段和视图。
11. 报表技巧
要了解用户通常是如何报告数据的:批处理还是在线提交报表?时间间隔是每天、每周、每月、
每个季度还是每年?如果需要的话还可以考虑创建总结表。系统生成的主键在报表中很难管理。
用户在具有系统生成主键的表内用副键进行检索往往会返回许多重复数据。这样的检索性能比较
低而且容易引起混乱。
—kol

对于普通报表的处理有三种实现,一种是直接写一个SQL查询,在查询中join多个表,形成报表的数据,第二种是写一个视图,在视图中实现报表所需的字段,第三种是为报表建立对应的表,然后由定时任务往这个表中填充数据,报表开发人员直接从该表出报表即可。第三种方式适用于不需要太实时,响应速度要快,而且存在大量汇总和数据量大的报表。
12. 理解客户需求
看起来这应该是显而易见的事,但需求就是来自客户(这里要从内部和外部客户的角度考虑)。
不要依赖用户写下来的需求,真正的需求在客户的脑袋里。你要让客户解释其需求,而且随着开
发的继续,还要经常询问客户保证其需求仍然在开发的目的之中。一个不变的真理是:“只有我
看见了我才知道我想要的是什么”必然会导致大量的返工,因为数据库没有达到客户从来没有写
下来的需求标准。而更糟的是你对他们需求的解释只属于你自己,而且可能是完全错误的。
—kgilson
这个无论对数据库模型设计人员还是应用程序开发人员都是一样的重要,不断去深化和理解客户的需求。


第2部分—设计表和字段
1. 检查各种变化
我在设计数据库的时候会考虑到哪些数据字段将来可能会发生变更。比方说,姓氏就是如此(注
意是西方人的姓氏,比如女性结婚后从夫姓等)。所以,在建立系统存储客户信息时,我倾向于
在单独的一个数据表里存储姓氏字段,而且还附加起始日和终止日等字段,这样就可以跟踪这一
数据条目的变化。
—Shropshire Lad

在中国一般不存在嫁人后改名的情况,所以国内的系统就用姓名连着写作为一个字段也没什么问题,更好一点的是把“曾用名”这个字段加上,以记录改名这种情况。我们使用的可能更加复杂,分别记录了英文姓,英文名,中文姓,中文名,英文姓名,中文姓名,显示名等,虽然有一定的冗余,不过查询起来方便。

2. 采用有意义的字段名
有一回我参加开发过一个项目,其中有从其他程序员那里继承的程序,那个程序员喜欢用屏幕上
显示数据指示用语命名字段,这也不赖,但不幸的是,她还喜欢用一些奇怪的命名法,其命名采
用了匈牙利命名和控制序号的组合形式,比如cbo1、 txt2、txt2_b 等等。
除非你在使用只面向你的缩写字段名的系统,否则请尽可能地把字段描述的清楚些。当然,也别
做过头了,比如Customer_Shipping_Address_Street_Line_1 I 虽然很富有说明性,但没人愿意
键入这么长的名字,具体尺度就在你的把握中。

—Lamont Adams

有意义的字段名可以使开发人员、数据库管理人员更容易理解字段的意义,不过我也遇到过特别变态的系统,表和字段使用顺序编号命名的,表就叫做TB_OBJECT_1090,TB_OBJECT_1092,字段就叫做F1,F2,F3的,必须对应数据库说明文档,感觉这样来保密,没有太大的必要。

3. 采用前缀命名
如果多个表里有好多同一类型的字段(比如FirstName),你不妨用特定表的前缀(比如
CusLastName)来帮助你标识字段。
—notoriousDOG

这个在我以前建模时有这样的习惯,比如CUSTOMER表,那么名字就叫CUSTOMER_NAME。但是由于现在才有了Automapping与对象映射,所以字段名都变得比较直接,去掉了前缀,直接使用NAME表示客户名字。

时效性数据应包括“最近更新日期/时间”字段。时间标记对查找数据问题的原因、按日期重新处
理/重载数据和清除旧数据特别有用。
—kol

在大部分表中,都添加了UPDATED_BY和UPDATED_TIME这两个字段,用于更重数据的最后修改人和最后修改时间。
5. 标准化和数据驱动
数据的标准化不仅方便了自己而且也方便了其他人。比方说,假如你的用户界面要访问外部数据
源(文件、XML文档、其他数据库等),你不妨把相应的连接和路径信息存储在用户界面支持表
里。还有,如果用户界面执行工作流之类的任务(发送邮件、打印信笺、修改记录状态等),那
么产生工作流的数据也可以存放在数据库里。预先安排总需要付出努力,但如果这些过程采用数
据驱动而非硬编码的方式,那么策略变更和维护都会方便得多。事实上,如果过程是数据驱动
的,你就可以把相当大的责任推给用户,由用户来维护自己的工作流过程。
—tduvall

如果是外部数据和文件,在本数据库中一般只保存个链接Url,而将文件的存储交个另一个系统或数据库,对于工作流也是,应该有一个独立的工作流数据库或者系统,我们的系统中只保留了一些状态信息。再比如用户和权限的配置和控制也可以独立在另一个数据库中。这样做就是为了数据库的解耦,不要将太多的不同业务的东西全部糅合在一起,尤其像工作流、权限这种通用的数据库。
6. 标准化不能过头
对那些不熟悉标准化一词(normalization )的人而言,标准化可以保证表内的字段都是最基础的
要素,而这一措施有助于消除数据库中的数据冗余。标准化有好几种形式,但Third Normal
Form(3NF)通常被认为在性能、扩展性和数据完整性方面达到了最好平衡。简单来说,3NF规
定:
· 表内的每一个值都只能被表达一次。
· 表内的每一行都应该被唯一的标识(有唯一键)。
· 表内不应该存储依赖于其他键的非键信息。
遵守3NF标准的数据库具有以下特点:有一组表专门存放通过键连接起来的关联数据。比方说,
某个存放客户及其有关定单的3NF数据库就可能有两个表:Customer和Order。Order表不包
含定单关联客户的任何信息,但表内会存放一个键值,该键指向Customer表里包含该客户信息
的那一行。
更高层次的标准化也有,但更标准是否就一定更好呢?答案是不一定。事实上,对某些项目来
说,甚至就连3NF都可能给数据库引入太高的复杂性。
—Lamont Adams
为了效率的缘故,对表不进行标准化有时也是必要的,这样的例子很多。曾经有个开发财务分析
软件的活就是用非标准化表把查询时间从平均40秒降低到了两秒左右。虽然我不得不这么做,
但我绝不把数据表的非标准化当作当然的设计理念。而具体的操作不过是一种派生。所以如果表
出了问题重新产生非标准化的表是完全可能的。
—epepke

什么时候该范式,什么时候反范式,这个没有必须的规则,只有在项目中根据实际情况进行决定,范式保证了数据的唯一性,反范式保证了查询的效率,如果不存在查询效率的时候,一般还是尽量范式化好些。
7. Microsoft Access报表技巧
如果你正在使用Microsoft Access,你可以用对用户友好的字段名来代替编号的名称:比如用
Customer Name代替txtCNaM。这样,当你用向导程序创建表单和报表时,其名字会让那些不
是程序员的人更容易阅读。
—jwoodruf

对Access不熟悉,但字段名友好这个是必须的。
8. 不活跃或者不采用的指示符
增加一个字段表示所在记录是否在业务中不再活跃挺有用的。不管是客户、员工还是其他什么
人,这样做都能有助于再运行查询的时候过滤活跃或者不活跃状态。同时还消除了新用户在采用
数据时所面临的一些问题,比如,某些记录可能不再为他们所用,再删除的时候可以起到一定 的
防范作用。
—theoden

这个没有在项目中用到,一来是数据量不大,二来是是否活跃不好判断,这个在互联网应用中使用比较多。增加了活跃字段后可以进行分区,这样查询活跃用户时会更快。
9. 使用角色实体定义属于某类别的列
在需要对属于特定类别或者具有特定角色的事物做定义时,可以用角色实体来创建特定的时间关
联关系,从而可以实现自我文档化。
这里的含义不是让PERSON实体带有Title字段,而是说,为什么不用PERSON实体和
PERSON_TYPE实体来描述人员呢?然后,比方说,当 John Smith, Engineer提升为John
Smith, Director乃至最后爬到John Smith, CIO的高位,而所有你要做的不过是改变两个表
PERSON和PERSON_TYPE之间关系的键值,同时增加一个日期/时间字段来知道变化是何时
发生的。这样,你的PERSON_TYPE表就包含了所有PERSON的可能类型,比如Associate、
Engineer、Director、CIO或者CEO等。
还有个替代办法就是改变PERSON记录来反映新头衔的变化,不过这样一来在时间上无法跟踪
个人所处位置的具体时间。
—teburlew

这个就是说将一对多的关系加上时间关系后变成多对多的关系,本来职级和员工是一对多的关系,一个员工只有一个职级,一个职级对应多个员工,但是加上时间维度,一个员工在很长一段时间来说,是对应多个职级的,对于要进行历史记录的字段,都可以这样操作。
10. 采用常用实体命名机构数据
组织数据的最简单办法就是采用常用名字,比如:PERSON、ORGANIZATION、ADDRESS和
PHONE等等。当你把这些常用的一般名字组合起来或者创建特定的相应副实体时,你就得到了
自己用的特殊版本。开始的时候采用一般术语的主要原因在于所有的具体用户都能对抽象事物具
体化。
有了这些抽象表示,你就可以在第2级标识中采用自己的特殊名称,比如,PERSON可能是
Employee、Spouse、Patient、Client、Customer、Vendor或者Teacher等。同样的,
ORGANIZATION也可能是MyCompany、MyDepartment、Competitor、Hospital、
Warehouse、Government等。最后ADDRESS可以具体为Site、Location、Home、Work、
Client、Vendor、Corporate和FieldOffice等。
采用一般抽象术语来标识“事物”的类别可以让你在关联数据以满足业务要求方面获得巨大的灵
活性,同时这样做还可以显著降低数据存储所需的冗余量。
—teburlew

命名规范的一部分吧,命名要清晰明了,不看数据库文档,只看表名、字段名就知道是什么意思。
11. 用户来自世界各地
在设计用到网络或者具有其他国际特性的数据库时,一定要记住大多数国家都有不同的字段格
式,比如邮政编码等,有些国家,比如新西兰就没有邮政编码一说。
—billh

这个确实是一个问题,各国的情况不一样,只有具体情况具体分析,一般我们考虑的是英语国家(英国美国)和中国。
12. 数据重复需要采用分立的数据表
如果你发现自己在重复输入数据,请创建新表和新的关系。
—Alan Rash

感觉这句话就是在说数据库设计中的第三范式。
13. 每个表中都应该添加的3个有用的字段
· dRecordCreationDate,在VB下默认是Now(),而在SQL Server下默认为GETDATE()
· sRecordCreator,在SQL Server下默认为NOT NULL DEFAULT USER
· nRecordVersion,记录的版本标记;有助于准确说明记录中出现null数据或者丢失数据的原因
—Peter Ritchie

我们一般对表都加了5个字段,创建人,创建时间,最后修改人,最后修改时间,是否删除。这些字段都用于审计功能,没有他说的版本标记。这个在记录历史数据的表中可能需要用到。
14. 对地址和电话采用多个字段
描述街道地址就短短一行记录是不够的。Address_Line1、Address_Line2和Address_Line3可
以提供更大的灵活性。还有,电话号码和邮件地址最好拥有自己的数据表,其间具有自身的类型
和标记类别。
—dwnerd
过分标准化可要小心,这样做可能会导致性能上出现问题。虽然地址和电话表分离通常可以达到
最佳状态,但是如果需要经常访问这类信息,或许在其父表中存放“首选”信息(比如
Customer等)更为妥当些。非标准化和加速访问之间的妥协是有一定意义的。
—dhattrem
这个要看具体的情况吧,如果我们自己设计的系统中,界面上就只允许输入一个联系地址,只允许输入一个联系电话,那么当然数据库也没有必要设计多个字段来存地址和电话了,这纯粹是个需求问题,简单的设计,只有一个字段,没什么不可的。
15. 使用多个名称字段
我觉得很吃惊,许多人在数据库里就给 name 留一个字段。我觉得只有刚入门的开发人员才会这
么做,但实际上网上这种做法非常普遍。我建议应该把姓氏和名字当作两个字段来处理,然后在
查询的时候再把他们组合起来。
—klempan
Klempan不是唯一一个注意到使用单个name 字段的人,要把这种情况变得对用户更为友好有好
些方法。我最常用的是在同一表中创建一个计算列,通过它可以自动地连接标准化后的字段,这
样数据变动的时候它也跟着变。不过,这样做在采用建模软件时得很机灵才行。总之,采用连接
字段的方式可以有效的隔离用户应用和开发人员界面。
—damon

我们设计的字段比他说的还要多,我个人并不赞成使用计算字段,这会增加数据库设计复杂性,而且在ORMapping时也很麻烦。个人更倾向于使用冗余的字段,也就是说姓、名、姓名三个字段都有,都存储,使用程序去控制其一致性。
16. 提防大小写混用的对象名和特殊字符
过去最令我恼火的事情之一就是数据库里有大小写混用的对象名,比如CustomerData。这一问
题从Access到Oracle数据库都存在。我不喜欢采用这种大小写混用的对象命名方法,结果还不
得不手工修改名字。想想看,这种数据库/应用程序能混到采用更强大数据库的那一天吗?采用全
部大写而且包含下划符的名字具有更好的可读性(CUSTOMER_DATA),绝对不要在对象名的
字符之间留空格。

—bfren

这一点在我们现在的项目中处理的非常好,因为我们命名规范就规定了数据库对象名全部大写,下划线分割。这个主要还是受Oracle的影响吧,我以前就喜欢大小写混合的方式。
17. 小心保留词
要保证你的字段名没有和保留词、数据库系统或者常用访问方法冲突,比如,最近我编写的一个
ODBC连接程序里有个表,其中就用了DESC作为说明字段名。后果可想而知!DESC是
DESCENDING缩写后的保留词。表里的一个SELECT *语句倒是能用,但我得到的却是一大堆
毫无用处的信息。
—Daniel Jordan

这个没也算是命名规则,我们经常在设计数据库的时候用到订单Order,用户组Group等,这些都是SQL的关键字,需要避免使用,可以通过加前缀或者后缀的方式解决。
18. 保持字段名和类型的一致性
在命名字段并为其指定数据类型的时候一定要保证一致性。假如字段在某个表中叫做
“agreement_number”,你就别在另一个表里把名字改成“ref1”。假如数据类型在一个表里
是整数,那在另一个表里可就别变成字符型了。记住,你干完自己的活了,其他人还要用你的数
据库呢。
—setanta

这个还是很有必要的,但并不是每个字段都要标识出类型,我们一般使用_CODE后缀表示是字符串,使用_DATE表示是日期类型,使用_TIME表示日期时间类型。在外键引用时一般保证字段名一致。
19. 仔细选择数字类型
在SQL中使用smallint和tinyint 类型要特别小心,比如,假如你想看看月销售总额,你的总额字
段类型是smallint,那么,如果总额超过了$32,767你就不能进行计算操作了。
—egermain

对于数字类型的选择是比较麻烦的,我们在项目中一般使用tinyint来表示枚举类型,使用bigint作为主键类型,使用int表示整数,小数一般使用decimal来表示,比如使用decimal(18,4)表示金额。如果要去更高精度的比如汇率,那就使用decimal(18,6)。
20. 删除标记
在表中包含一个“删除标记”字段,这样就可以把行标记为删除。在关系数据库里不要单独删除
某一行;最好采用清除数据程序而且要仔细维护索引整体性。
—kol
前面说到我们一般在表中添加了5个字段,其中一个字段就是IS_DELETED,是一个布尔类型,用于表示该行数据是否被删除。
21. 避免使用触发器
触发器的功能通常可以用其他方式实现。在调试程序时触发器可能成为干扰。假如你确实需要采
用触发器,你最好集中对它文档化。
—kol

我们在项目中就根本没有用过触发器,以至于如果要用触发器我都要去查一下文档,看看触发器怎么写了。我坚持认为数据的更改都应该在程序的事务中完成,触发器这种东西不为应用程序所知,开发人员也不会注意,会导致很多未知的问题。所以我们是完全不用触发器。
22. 包含版本机制
建议你在数据库中引入版本控制机制来确定使用中的数据库的版本。无论如何你都要实现这一要
求。时间一长,用户的需求总是会改变的。最终可能会要求修改数据库结构。虽然你可以通过检
查新字段或者索引来确定数据库结构的版本,但我发现把版本信息直接存放到数据库中不更为方
便吗?。
—Richard Foster

没有把版本信息存放在数据库中,完全适应TFS等源代码管理工具来控制Schema脚本的版本。
23. 给文本字段留足余量
ID类型的文本字段,比如客户ID或定单号等等都应该设置得比一般想象更大,因为时间不长你
多半就会因为要添加额外的字符而难堪不已。比方说,假设你的客户ID为10位数长。那你应该
把数据库表字段的长度设为12或者13个字符长。这算浪费空间吗?是有一点,但也没你想象的
那么多:一个字段加长3个字符在有1百万条记录,再加上一点索引的情况下才不过让整个数据
库多占据3MB的空间。但这额外占据的空间却无需将来重构整个数据库就可以实现数据库规模
的增长了。
—tlundin

这个是我之前经常犯的错误,出于性能的考虑,在设置字符串长度时总是很抠门的设置的很小,但是在后来开发中就经常遇到字符串被截断的异常,不得不一次又一次的改Schema增加字符串长度。(比如姓名字段,4个汉字已经够长了吧,在使用复姓的情况下也就4个汉字,结果哪知道还有可能有少数民族的人的姓名可能超过4个字的情况)后来吸取教训,字符串的长度都设置的长一些,由于是变长字符串,所以在数据库的内部存储时并不会真正增加多少存储空间。

24. 列命名技巧
我们发现,假如你给每个表的列名都采用统一的前缀,那么在编写SQL表达式的时候会得到大
大的简化。这样做也确实有缺点,比如破坏了自动表连接工具的作用,后者把公共列名同某些数
据库联系起来,不过就连这些工具有时不也连接错误嘛。举个简单的例子,假设有两个表:
Customer和Order。Customer表的前缀是cu_,所以该表内的子段名如下:cu_name_id、
cu_surname、cu_initials和cu_address等。Order表的前缀是or_,所以子段名是:
or_order_id、or_cust_name_id、or_quantity和or_description等。
这样从数据库中选出全部数据的SQL语句可以写成如下所示:
Select * from Customer, Order
Where cu_surname = "MYNAME"
and cu_name_id = or_cust_name_id
and or_quantity = 1;
在没有这些前缀的情况下则写成这个样子:
Select * from Customer, Order
Where Customer.surname = "MYNAME"
and Customer.name_id = Order.cust_name_id
and Order.quantity = 1
第1个SQL语句没少键入多少字符。但如果查询涉及到5个表乃至更多的列你就知道这个技巧
多有用了。
—Bryce Stenberg
列名中包含表名作为前缀是我以前常用的设计方法,但是在ORMapping的AutoMapping时不是很实用,因为这样映射出来的class就不是很好看。如果不采用ORMapping的情况下,我还是建议为每个字段都唯一命名,在编写SQL查询时会比较方便。


第3部分—选择键和索引
1. 数据采掘要预先计划
我所在的市场部门一度要处理8万多份联系方式,同时填写每个客户的必要数据(这绝对不是小
活)。我从中还要确定出一组客户作为市场目标。当我从最开始设计表和字段的时候,我试图不
在主索引里增加太多的字段以便加快数据库的运行速度。然后我意识到特定的组查询和信息采掘
既不准确速度也不快。结果只好在主索引中重建而且合并了数据字段。我发现有一个指示计划相
当关键——当我想创建系统类型查找时为什么要采用号码作为主索引字段呢?我可以用传真号码
进行检索,但是它几乎就象系统类型一样对我来说并不重要。采用后者作为主字段,数据库更新
后重新索引和检索就快多了。
—hscovell
可操作数据仓库(ODS)和数据仓库(DW)这两种环境下的数据索引是有差别的。在DW环境
下,你要考虑销售部门是如何组织销售活动的。他们并不是数据库管理员,但是他们确定表内的
键信息。这里设计人员或者数据库工作人员应该分析数据库结构从而确定出性能和正确输出之间
的最佳条件。
—teburlew

关于索引的计划不是很好在数据库设计时就能定下来的,只有在系统开发过程中,根据需求调整索引。索引的调整是个动态的过程,随着需求的变化,可能索引也会不断变化。
2. 使用系统生成的主键
这一点类同技巧1,但我觉得有必要在这里重复提醒大家。假如你总是在设计数据库的时候采用
系统生成的键作为主键,那么你实际控制了数据库的索引完整性。这样,数据库和非人工机制就
有效地控制了对存储数据中每一行的访问。
采用系统生成键作为主键还有一个优点:当你拥有一致的键结构时,找到逻辑缺陷很容易。
—teburlew

如果使用oracle数据库,那么我们就用Sequence来生成主键,如果使用mysql或者SQL Server,那么就使用NHibernate的Hilo生成,当然也使用用GUID来作为SQL Server的主键,因为SQL Server中有一种数据类型叫做uniqueidentifier,存放GUID非常方便,当然也可以使用SQL Server的identity来标识自增的主键列。不过我们更倾向于使用Hilo生成,这样便于分布式的数据库应用。
3. 分解字段用于索引
为了分离命名字段和包含字段以支持用户定义的报表,请考虑分解其他字段(甚至主键)为其组
成要素以便用户可以对其进行索引。索引将加快SQL和报表生成器脚本的执行速度。比方说,
我通常在必须使用SQL LIKE表达式的情况下创建报表,因为 case number字段无法分解为
year、serial number、case type和defendant code等要素。性能也会变坏。假如年度和类型字
段可以分解为索引字段那么这些报表运行起来就会快多了。
—rdelval

这个感觉是在做OLAP时,需要重新设计模型,分解OLTP模型中的字段,增加冗余字段,使得出报表的查询效率更高。
4. 键设计4原则
· 为关联字段创建外键。
· 所有的键都必须唯一。
· 避免使用复合键。
· 外键总是关联唯一的键字段。
—Peter Ritchie
如果是应用系统对应的数据库,尽量使用一个字段作为主键,只有某些情况才使用复合主键,比如在多对多生成的中间表,则个中间表只有两个字段,两个字段组成复合主键。不过在应用系统中,纯粹的多对多情况并不是很多,一般都会在多对多时在中间表中添加一些属性,形成一个新的对象,那么这个对象就需要使用一个单独的主键字段。
5. 别忘了索引
索引是从数据库中获取数据的最高效方式之一。95%的数据库性能问题都可以采用索引技术得到
解决。作为一条规则,我通常对逻辑主键使用唯一的成组索引,对系统键(作为存储过程)采用
唯一的非成组索引,对任何外键列采用非成组索引。不过,索引就象是盐,太多了菜就篌了。你
得考虑数据库的空间有多大,表如何进行访问,还有这些访问是否主要用作读写。
—tduvall
大多数数据库都索引自动创建的主键字段,但是可别忘了索引外键,它们也是经常使用的键,比
如运行查询显示主表和所有关联表的某条记录就用得上。还有,不要索引memo/note字段,不
要索引大型字段(有很多字符),这样作会让索引占用太多的存储空间。
—gbrayton

索引确实非常非常的重要,但是在建模时一般不会太考虑索引,只需要对主键和外键建立索引即可,毕竟索引是根据实际的查询来决定的,在不知道查询条件时也没办法建立对应的索引,所以很多索引都是在开发和测试的过程中才建立的。
6. 不要索引常用的小型表
不要为小型数据表设置任何键,假如它们经常有插入和删除操作就更别这样作了。对这些插入和
删除操作的索引维护可能比扫描表空间消耗更多的时间。
—kbpatel

主键和对应的聚集索引还是必要的,如果数据量小,那么在调优时也不会太多的考虑在该表上建立索引。
7. 不要把社会保障号码(SSN)选作键
永远都不要使用SSN作为数据库的键。除了隐私原因以外,须知政府越来越趋向于不准许把
SSN用作除收入相关以外的其他目的,SSN需要手工输入。永远不要使用手工输入的键作为主
键,因为一旦你输入错误,你唯一能做的就是删除整个记录然后从头开始。
—teburlew
上个世纪70年代我还在读大学的时候,我记得那时SSN还曾被用做学号,当然尽管这么做是非
法的。而且人们也都知道这是非法的,但他们已经习惯了。后来,随着盗取身份犯罪案件的增
加,我现在的大学校园正痛苦地从一大摊子数据中把SSN删除。
—generalist

在中国就是不要把身份证号作为主键,虽然身份证号码是唯一的,但是作为主键有以下几个缺点:

1用户输入的,不保证用户一次输入肯定正确,如果输入错误就需要进行更改,也就意味着改主键字段,这很麻烦。

2身份证号码不是自增的,对应建立聚集索引容易导致BTree的修改。

3身份证号太长了,用字符串表示得18位,比用int类型的4位长了不少,效率相对较低。

4身份证号应该是隐私的,里面包含了出生地、生日、性别等信息,有些时候这些信息是不希望用户看到的。
8. 不要用用户的键
在确定采用什么字段作为表的键的时候,可一定要小心用户将要编辑的字段。通常的情况下不要
选择用户可编辑的字段作为键。这样做会迫使你采取以下两个措施:
· 在创建记录之后对用户编辑字段的行为施加限制。假如你这么做了,你可能会发现你的应用程
序在商务需求突然发生变化,而用户需要编辑那些不可编辑的字段时缺乏足够的灵活性。当用
户在输入数据之后直到保存记录才发现系统出了问题他们该怎么想?删除重建?假如记录不可
重建是否让用户走开?
· 提出一些检测和纠正键冲突的方法。通常,费点精力也就搞定了,但是从性能上来看这样做的
代价就比较大了。还有,键的纠正可能会迫使你突破你的数据和商业/用户界面层之间的隔
离。
所以还是重提一句老话:你的设计要适应用户而不是让用户来适应你的设计。
—Lamont Adams
不让主键具有可更新性的原因是在关系模式下,主键实现了不同表之间的关联。比如,
Customer表有一个主键CustomerID,而客户的定单则存放在另一个表里。Order表的主键可能
是OrderNo或者OrderNo、CustomerID和日期的组合。不管你选择哪种键设置,你都需要在
Order表中存放CustomerID来保证你可以给下定单的用户找到其定单记录。
假如你在Customer表里修改了CustomerID,那么你必须找出Order表中的所有相关记录对其进
行修改。否则,有些定单就会不属于任何客户——数据库的完整性就算完蛋了。
如果索引完整性规则施加到表一级,那么在不编写大量代码和附加删除记录的情况下几乎不可能
改变某一条记录的键和数据库内所有关联的记录。而这一过程往往错误丛生所以应该尽量避免。
—ljboast

这个就是和前面说的用身份证号码作为主键的缺点中说到的一样,4个缺点都可能会有,所以不建议用。
9. 可选键有时可做主键
记住,查询数据的不是机器而是人。
假如你有可选键,你可能进一步把它用做主键。那样的话,你就拥有了建立强大索引的能力。这
样可以阻止使用数据库的人不得不连接数据库从而恰当的过滤数据。在严格控制域表的数据库
上,这种负载是比较醒目的。如果可选键真正有用,那就是达到了主键的水准。
我的看法是,假如你有可选键,比如国家表内的state_code,你不要在现有不能变动的唯一键上
创建后续的键。你要做的无非是创建毫无价值的数据。比如以下的例子:
Select count(*)
from address, state_ref
where
address.state_id = state_ref.state_id
and state_ref.state_code = 'TN'
我的做法是这样的:
Select count(*)
from address
where
and state_code = 'TN'
如你因为过度使用表的后续键建立这种表的关联,操作负载真得需要考虑一下了。
—Stocker

可选键在数据库设计时还是有必要的,一方面可以约束数据符合业务需求,另一方面可以为可选键建立索引增加查询的效率。
10. 别忘了外键
大多数数据库索引自动创建的主键字段。但别忘了索引外键字段,它们在你想查询主表中的记录
及其关联记录时每次都会用到。还有,不要索引memo/notes字段而且不要索引大型文本字段
(许多字符),这样做会让你的索引占据大量的数据库空间。。
—gbrayton
我对外键的看法是,外键应该只在开发和测试环境中建,对于生产环境,还是取消所有外键比较好,主要是在生产环境下,数据量比较大,取消外键可以提高增删改的效率,数据之间的约束在程序中维护。在开发环境和测试环境中保留外键就是为了保证程序在操作数据库时遵守外键约束,只要在开发测试环境中程序对数据库的操作是正确的,那么在生成环境没有外键约束的情况下也是仍然是正常的。
第4部分—保证数据的完整性
1. 用约束而非商务规则强制数据完整性
如果你按照商务规则来处理需求,那么你应当检查商务层次/用户界面:如果商务规则以后发生变
化,那么只需要进行更新即可。
假如需求源于维护数据完整性的需要,那么在数据库层面上需要施加限制条件。
如果你在数据层确实采用了约束,你要保证有办法把更新不能通过约束检查的原因采用用户理解
的语言通知用户界面。除非你 的字段命名很冗长,否则字段名本身还不够。
—Lamont Adams
只要有可能,请采用数据库系统实现数据的完整性。这不但包括通过标准化实现的完整性而且还
包括数据的功能性。在写数据的时候还可以增加触发器来保证数据的正确性。不要依赖于商务层
保证数据完整性;它不能保证表之间(外键)的完整性所以不能强加于其他完整性规则之上。
—Peter Ritchie

不是完全不同意这个观点,约束会降低数据更新时的执行效率,约束应该在开发和测试环境的数据库中存在,在生产环境中,只需要主键即可,其他的外键约束、check约束都是浮云,全部去掉。
2. 分布式数据系统
对分布式系统而言,在你决定是否在各个站点复制所有数据还是把数据保存在一个地方之前应该
估计一下未来5年或者10年的数据量。当你把数据传送到其他站点的时候,最好在数据库字段
中设置一些标记。在目的站点收到你的数据之后更新你的标记。为了进行这种数据传输,请写下
你自己的批处理或者调度程序以特定时间间隔运行而不要让用户在每天的工作后传输数据。本地
拷贝你的维护数据,比如计算常数和利息率等,设置版本号保证数据在每个站点都完全一致。
— Suhair TechRepublic

在数据库主键设计中使用Hilo或者GUID就是为了考虑分布式数据系统。这样的主键可以将多个分布数据库中的数据合并到一个系统中,而主键不会发生冲突。
3. 强制指示完整性
没有好办法能在有害数据进入数据库之后消除它,所以你应该在它进入数据库之前将其剔除。激
活数据库系统的指示完整性特性。这样可以保持数据的清洁而能迫使开发人员投入更多的时间处
理错误条件。
—kol

这就是在开发和测试环境中保留约束的原因,各种完整性的约束可以帮助开发人员编写其代码逻辑。
4. 关系
如果两个实体之间存在多对一关系,而且还有可能转化为多对多关系,那么你最好一开始就设置
成多对多关系。从现有的多对一关系转变为多对多关系比一开始就是多对多关系要难得多。
—CS Data Architect

没什么好说的了,可能多对多的,那么就得设计成多对多,不然以后再想把一对多改成多对多会很麻烦。
5. 采用视图
为了在你的数据库和你的应用程序代码之间提供另一层抽象,你可以为你的应用程序建立专门的
视图而不必非要应用程序直接访问数据表。这样做还等于在处理数据库变更时给你提供了更多的
自由。
—Gay Howe
这个在项目中使用的比较好,因为很多查询可能会涉及到多个表的join还有count,max,min等聚集函数的操作,由于使用到了ORMapping的工具,所以要在代码中实现这些查询是很麻烦的,所以最好的办法就是建立一个视图,然后和一个类映射起来,在代码中就像查询表一样的查询这个类即可,所有的Join和聚集函数等操作都对程序开发人员完全屏蔽。

6. 给数据保有和恢复制定计划
考虑数据保有策略并包含在设计过程中,预先设计你的数据恢复过程。采用可以发布给用户/开发
人员的数据字典实现方便的数据识别同时保证对数据源文档化。编写在线更新来“更新查询”供
以后万一数据丢失可以重新处理更新。
—kol

这个是DBA的日常工作之一,保持日常的数据库备份,一般是每日一备份,一方面可以保证数据安全,预防不测,另一方面可以方便调试一些在生产环境发生的Bug,查看历史数据的变动情况。
7. 用存储过程让系统做重活
解决了许多麻烦来产生一个具有高度完整性的数据库解决方案之后,我所在的团队决定封装一些
关联表的功能组,提供一整套常规的存储过程来访问各组以便加快速度和简化客户程序代码的开
发。在此期间,我们发现3GL编码器设置了所有可能的错误条件,比如以下所示:
SELECT Cnt = COUNT (*)
FROM [

]
WHERE [] =
IF Cnt = 0
BEGIN
INSERT INTO [
]
( [] )
VALUES ( )
END
ELSE
BEGIN

END
而一个非3GL编码器是这样做的:
INSERT INTO [
]
( [] )
VALUES
( )
IF @@ERROR = 2627 -- Literal error code for Primary Key Constraint
BEGIN

END
第2个程序简单多了,而且事实上,利用了我们给数据库的功能。虽然我个人不喜欢使用嵌入文
字(2627)。但是那样可以很方便地用一点预先处理来代替。数据库不只是一个存放数据的地
方,它也是简化编码之地。
—a-smith

在使用了NHibernate或者EntityFramework这种ORMapping工具后,存储过程在应用程序端就很少使用了,但是存储过程仍然被用于数据同步、批量更新等数据量大,逻辑比较复杂的操作。
8. 使用查找
控制数据完整性的最佳方式就是限制用户的选择。只要有可能都应该提供给用户一个清晰的价值
列表供其选择。这样将减少键入代码的错误和误解同时提供数据的一致性。某些公共数据特别适
合查找:国家代码、状态代码等。
—CS Data Architect
这个就是需要建立一个Classification表,该表中保存各种码表数据,对于有些界面字段,就从该表中获取值,用户只能从这些值中选择,然后将其值存入对应的数据库表中。
第5部分—各种小技巧
1. 文档、文档、文档
对所有的快捷方式、命名规范、限制和函数都要编制文档。
—nickypendragon
采用给表、列、触发器等加注释的数据库工具。是的,这有点费事,但从长远来看,这样做对开
发、支持和跟踪修改非常有用。
—chardove
取决于你使用的数据库系统,可能有一些软件会给你一些供你很快上手的文档。你可能希望先开
始在说,然后获得越来越多的细节。或者你可能希望周期性的预排,在输入新数据同时随着你的
进展对每一部分细节化。不管你选择哪种方式,总要对你的数据库文档化,或者在数据库自身的
内部或者单独建立文档。这样,当你过了一年多时间后再回过头来做第2个版本,你犯错的机会
将大大减少。
—mrs_helm

没什么好说的,PowerDesigner可以解决文档的问题,当然,维护这样一个文档也是需要很多的时间和精力的。
2. 使用常用英语(或者其他任何语言)而不要使用编码
为什么我们经常采用编码(比如9935A可能是墨水笔的供应代码,4XF788-Q可能是帐目编
码)?理由很多。但是用户通常都用英语进行思考而不是编码。工作5年的会计或许知道
4XF788-Q是什么东西,但新来的可就不一定了。在创建下拉菜单、列表、报表时最好按照英语
名排序。假如你需要编码,那你可以在编码旁附上用户知道的英语。
—amasa

一般来说使用英语进行命名,实在不行就使用拼音全拼来命名,只要能过自解释就好。
3. 保存常用信息
让一个表专门存放一般数据库信息非常有用。我常在这个表里存放数据库当前版本、最近检查/修
复(对Access)、关联设计文档的名称、客户等信息。这样可以实现一种简单机制跟踪数据
库,当客户抱怨他们的数据库没有达到希望的要求而与你联系时,这样做对非客户机/服务器环境
特别有用。
—Richard Foster

这个我没有做到,不过我们可以将数据库Schema的脚本维护在源代码管理中,通过源代码管理工具来控制版本,不过确实没有在数据库中直接维护一个表保存这些数据直观。
4. 测试、测试、反复测试
建立或者修订数据库之后,必须用用户新输入的数据测试数据字段。最重要的是,让用户进行测
试并且同用户一道保证你选择的数据类型满足商业要求。测试需要在把新数据库投入实际服务之
前完成。
—juneebug

在数据库中进行单元测试是很困难的,由于我们采用了ORMapping,所以可以在代码中直接写UT,每对数据库进行更改时可以运行所有相关的UT,保证更改的正确。
5. 检查设计
在开发期间检查数据库设计的常用技术是通过其所支持的应用程序原型检查数据库。换句话说,
针对每一种最终表达数据的原型应用,保证你检查了数据模型并且查看如何取出数据。
—jgootee
6. Access设计技巧
对复杂的Microsoft Access数据库应用程序而言,可以把所有的主表放在一个数据库文件里,然
后增加其他数据库文件和装载同原有数据库有关的特殊函数。根据需要用这些函数连接到主文件
中的主表。比如数据输入、数据QC、统计分析、向管理层或者政府部门提供报表以及各类只读
查询等。这一措施简化了用户和组权限的分配,而且有利于应用程序函数的分组和划分,从而在
程序必须修改的时候易于管理。
—Dennis Walden

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn