版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://alek.blogbus.com/logs/5120028.html
有以下几个诀窍:
1、业务主键和数据库物理主键分离
在抽象系统业务模型时,我们都习惯于根据业务的情况定义出一个表的唯一不重复的关键字(主键)。然而,更好的做法,是不论具体业务如何,把所有的表的主键都定义成Generator支持下的自增长整数,作为记录编号(命名为xxx_key),把“业务主键”定义为不可重复的索引(甚至于根本不用定义,如果没有特殊的检索要求的话)。在这种主键设计策略中,隐含着一个很深的道理,那就是“将业务逻辑和物理逻辑脱离开”的原则,实体的业务逻辑并不直接运用到真实的物理逻辑实现上。以单据表作为一个典型的例子,单据往往具备着一个不可重复的“单据编号”,一般它的数据类型是一个几十字节的字符串,它相当于是“单据”这个实体在业务逻辑上体现出来的主键。而在这里的规范中,却并不以这个字符串编号为真正物理上的主键,而是采用一个自增长的KEY取代它成为真正的物理上的主键。以此为基础,表与表之间的关联也同样以一个单独整数字段进行关联,这个字段对于主表,一般就是主表的主键字段。在外键的创建中,一般也尽量避免多字段关联的联合外键。在FireBird数据库中,这种设计方式利用了FireBird特有的生成器(Generator)机制来实现。在创建外键时,一般都将外键的cascade delete打开,利用数据库的级联删除功能达到了自动删除从表记录。
2、 索引的建立
要为可能频繁发生的检索的条件组合建立相应的索引。如果定义了主从表的外键关联,那么系统会自动创建所需的索引。但针对不同的数据类型,索引的执行效率也是不同的。对于复杂的查询,可以通过plan子句告诉服务器采用怎样的索引进行数据检索,格式如下:
Select语句
PLAN(
表名1 INDEX(索引名称),
表名2 INDEX(索引名称)
)
3、 自增长字段的建立
建立表的时候,在IBExpert中通过打开字段的AutoInc属性设定某字段是自增长字段后,同时建立相应的生成器和触发器;通过调用SQL语句来得到服务器端生成器的值并使之加1:
Select gen_id(AGenerator,1) as id from rdb$database用一个ibquery控件来执行该语句产生永不重复整数序列。
一般情况下,一个表对应着一个生成器。但是在某些情况下,可能多个表共用一个生成器,也可能一个表用多个生成器
4、应尽量避免字符串字段作为关联查询的关联条件。
5、应尽量避免多重字段、字符串字段(特别是常字符串)作为主键、关联查询的关联字段
6、在系统构架方面应该采取划分业务数据生存期的方式,在系统中采用动态创建数据库的机制,使不同的业务区间对应一个单独的数据库文件,比如说,一个会计核算年对应一个GDB文件,到年底核算时,为下年创建一个新的数据库,新数据库中的常量数据从老数据库中继承过来,而动态的业务数据从0开始。划分动态数据库分区的原则就是,把每个数据库文件大约控制在600M左右(更大些也可以,但不太利于备份刻录到光盘上)
看了你的经验收益良多. | |
TIME: 2006-12-2 14:05:31 IP: Protected .NET时代的win32英雄 |
广泛的使用自增长整数作为主键,是不是会在大批量数据的导入时遇到很大的麻烦?
如果两表关联还是用的业务主键相关联 那么逻辑主键似乎没有存在的必要
逻辑主键存在的必要就是提供检索
==============================================
两表用逻辑主键相关联 这样的确很大程度的精简了两表关联的SQL处理
可是会不会造成两表关联的不正确呢?
说的非常好。在数据导入过程中,尤其是从逻辑主键的设计升级到物理主键的设计时,两表的关联确实需要一个将逻辑主键关联转化为物理主键关联的过程。处理这种做法,我有两个方法。
其一就是客户端处理,方法是在客户端放一个内存表,预先载入被关联的基础数据表,其中既包含物理主键,又包含逻辑主键(别的字段可以省略),然后进行关联表的导入,导入时,每插入一条记录之前,都通过内存表查一下逻辑主键对应的物理主键的值,把该值放入记录相应的关联字段中,再插入数据库。
第二就是服务器端处理。是在服务器端的触发器中判断如果整数关联字段为空,那么自动通过原始的逻辑主键关联字段查出物理关联字段对应的值,赋值进去。意思差不多。
如果是系统本身就采用了这种物理主键的思想进行设计的情况,那么一切都OK,只不过在数据导入导出过程中需要注意两个问题:
1、尊重已形成的自增长主键的值,也就是,已形成的自增长主键的值不能让它重新生成,是多少就是多少,原样导出,原样倒入,这样就不会错乱。IBExpert生成的触发器因为有判断语句,所以并不会干扰。
2、生成器对象的值也要随着变。特别是,在空数据库倒入历史数据时,一定要把生成器的值也继承过来。
注意了这两点,你就能对我提出的“业务主键和数据库物理主键分离”的思想运用自如,设计出的系统也有很好的性能。
我们公司的会计软件就是这样做的。 | |
TIME: 2007-2-13 18:28:21 IP: Protected |
可以请yangdong兄介绍一下“数据集合并法”吗? | |
TIME: 2007-2-13 18:29:16 IP: Protected |
某位仁兄的疑问: | |
TIME: 2007-2-13 18:32:54 IP: Protected |
对于采用流水库,我有一个疑问,那就是在建立索引的情况下,查找第一个记录和最后一个记录的时间是一样的,不管其数据量多么庞大,因为记录指针的表示范围是不变的,指针驱动磁盘走向到某个扇区去读取数据,只要不是跨设备读取,就不会什么效率问题出现。因此,采用流水库是不是一个多余的做法? | |
TIME: 2007-2-13 18:50:35 IP: Protected |
数据库的崩溃是否与数据库容量密切相关?在其他问题(频繁停电、、电磁干扰和频繁使用等)得不到解决的情况下,一个月或者更短时间建立一个数据库即可解决问题? | |
TIME: 2007-2-18 11:29:06 IP: Protected |
如果那个车牌系统换一个思路,采用我说的Firebird加上流水库的机制,数据库结构设计得当,定期滚动删除动态创建的gdb文件,而且同样是把图片保存到blob中,这样设计,我怀疑,系统用个十来年也出不了什么问题。
从那次经历,我也体验到,考验数据库平台的承受极限还真挺容易,不良的设计即便是对于SQL Server这样精良打造的数据库也能够致命。
不知道具体瑕疵是怎么出来的,如果定期备份后清空数据库,瑕疵就不会积累,数据资料也可妥善保存,分批保存的资料都可以调出使用。
===================================================
问题就在这里了。如果要达到无人值守的程度,也就是说将数据库维护“傻瓜化”,在现场没有专门的数据管理员的情况下系统能够健壮的被使用,那么“定期备份后清空数据库”的动作就要求高了。另外,按照时间建立新库的另一个着眼点就是性能方面,由于历史数据永远在以前的数据库中,所以单库的查询速度就完全不会因为历史数据的增加而受影响。
当然,按照“理论”来讲,如果建立了索引,那么数据量增加也不会按照正比的幅度降低系统性能,而分库的做法是完全消除了这种因素。但是毕竟,动态建库的风格并不是为了某个特定的目地而提出的。这种风格的设计带来的是一连串的因素的变化,这一点恐怕得在实际设计和运行中才能真正体会、理解到,其用意不是那么简单、具体。概括的说,分库设计的目地,就是把数据库从“时间积累性因素”中脱出来。“时间积累性因素”会在系统的生命周期中衍生出一系列的消极因素来,降低系统的性能和稳定性。
当然,你所说的类似的方法我还能进一步引申,那就是制作自动备份恢复数据库的服务,定时对数据库进行备份+恢复,每次备份恢复后,gdb文件肯定是很干净、紧缩的库结构,数据不丢失,这样看来,好像是“完美了”,然而,从设计的直觉艺术来看,总是给我感觉隐隐的有些不足,最大一点就是,仍然没有彻底的摆脱“时间积累性因素”的阴影。打个不恰当的比方,就是,我感觉动态建库的思路设计的系统用500年问题不大,而用传统的单库设计我不敢这么保证。当然,这是一个比方。
[该帖子由作者于2007年2月18日 19:45:19最后编辑]
百度的数据库中存储了数十亿网页,查询的速度很快,而且我从没有感觉到它崩溃过,不知怎么弄的。
我解释一下你可能会感觉吃惊:这些搜索引擎ISP使用的存储介质,其实根本不是常规意义的数据库,而是根据关键词的网络结构加上分布式文件存储,是ISP自己实现的存储格式,有点像神经元的原理。从存储上,这些isp采用的分布式,而且存储内容的分布式布局都是为搜索而优化的;从检索索引上来看,搜索引擎索引体现的是特殊的网络状数据结构,并不是像我们所想象的放一个SQL Server,搜索的时候执行select where content like '%xxx%',比这个复杂很多倍。如果是靠数据库的搜索功能,恐怕10万页面就到秒级速度了。
中学食堂刷卡用的计算机不可能总是开的,通常情况应该是使用前打开,用完后关闭,关闭计算机前做些维护工作应该并不困难。有些农村地区电压波动很大,瑕疵估计主要是由于电压波动和停电所致。一些精密的仪器往往需要稳压电源,以前买计算机也要配稳压电源的,不应该随着计算机的价格下降就随便降低它的使用条件。
=========================================================
“关闭计算机前做些维护工作应该并不困难”,那是对咱们不困难。可是某些用户却恨不得点一个按钮什么事情都干了,或者恨不得把机器打开,系统就能用,点一个按钮就出报表,剩下的他们什么都不管。对于开发人员来说, 备份数据库司空见惯。然而对于某类用户来说, “做些维护工作”和“免维护”差别太大了。当然,如果是大规模的外企,一般都有专门的DBA,那么你不给他找点事儿他反而无聊。但如果不是这么实力雄厚的企业用户,可就不一定不在乎“做些维护工作”了。
当然,这是一个极端的比方。该买各种设备的当然还得买。只怕UPS、屏蔽罩买了,它又会出现其它意料外事情。用户现场可能会出现各种开发人员意想不到的状况,会造成这类按理说永远不该发生的情况。
负责前台机器维护的是售货员或基层管理人员,而收集的数据需要送到财务部门和管理部门,这些数据将用来核查帐目、财务管理和监督。前台收集来的数据不能随便改动,前台机器的操作必须非常简单,而且操作不能影响数据库中已有的内容。维护数据库的工作由财务部门和管理部门负责,受过中等教育的人不难对数据库进行例行的备份工作,在前台机器与后台数据库之间可设一个开关。
==========================================================
你说的是理想状态。在实际中,你的客户可能就一个主要部门,只有一个操作员兼财务主管。通常这种规模的企业用户是无法划拨专门的数据库管理员的,几十人的小型企业很难有那么分明的部门,他们最希望的就是所用软件系统的简单和透明,不喜欢任何莫名其妙的东西。所以说,我提倡一个概念,就是“数据库系统工具化”、“数据库文档化”,也就是说,数据库系统要做的象Office那样让人一用就会,创建、维护数据库就像操作一个office文档那么容易和轻松。其实Access从易用性上已经基本达到这个程度了。只不过我想让Client/Server数据库也达到这样的效果,在网络环境中创建数据库就像创建一个文档一样容易,在系统的功能设计中,屏蔽任何与业务无关的技术成分。FireBird/Interbase在这方面恰恰有利于实现这个目标,特别是2个重要因素:1、单文件数据库;2、小脚印(客户端只要一个动态库文件)。
受过中等教育的人不难对数据库进行例行的备份工作
==========================================================
你不能老用程序员的思路去揣测用户啊!你觉得“不难”不代表用户也这么认为。我作为开发人员有8年多的开发经历了,还觉得用IBExpert备份、恢复某个GDB库很麻烦呢,光鼠标键盘联合操作就一大串子,更何况用户乎?某些场合,让用户多点一下鼠标,用户都觉得很烦。任何一个操作环节都是可能出现问题的源头。系统能够做到免去这一环节就一定有它的意义和价值,当然不是说不应该提供备份恢复功能,可以把这一功能进行一定的封装,使其自动化。我们可以提供一次性点击鼠标完成备份恢复,但这个和流水库机制是两个领域的东西,谈不上谁替代谁。
[该帖子由作者于2007年2月19日 14:32:02最后编辑]
关于数据库瑕疵方面,不一定是停电和电压不稳才能造成。在某些情况下,例如用户的数据操作引起数据库服务进程死锁、太大的Transaction、数据库服务进程崩溃、栈溢出、操作系统蓝屏都会带来数据库的坏簇。
有那些清理数据库中瑕疵的办法?
==================================
你所说的备份/恢复的方法就是清理数据库瑕疵的最典型的方法。事实上InterBase/FireBird数据库就提倡经常对数据库进行备份恢复,不仅能发现和修正坏的簇,而且还能清理冗余页面。这种维护活动对于基于访问单个库的系统尤为重要。所有的数据库都提供了备份恢复以及数据导入导出功能。
那些数据库容错能力强?数据库崩溃后怎么处理? | |
TIME: 2007-2-19 21:32:10 IP: Protected |
我觉得数据库中的瑕疵主要来自卖饭时数据的录入过程,如果将卖饭时录入的数据先存于一个公文包中,在检查无误后再将数据转存数据库中,可达到减少数据库中瑕疵的效果。
不是录入,是接工业总线,接收从售饭机里面发出的数据包,进行数据库验证查询,然后将消费记录存入数据库。
确实是这样。用公文包也能解决问题,相当于把数据暂存起来。但是又出现一个新的问题,那就是无法在多个监听服务实例运行时验证余额,防止透支,这样的话,那么要么只能有一个服务,要么可以允许多个监听服务,但不验证余额透支。我举这个例子是从数据库的角度来看这种问题,怎样通过加固数据库来加固系统。而针对这个具体案例来说,解决方案很多,不一定是以数据库为出发点,比如果,做一个以共享内存为基础的监听服务即可。
你举那个例子是为了说明“流水数据库”,大概意思是编写一个程序使数据库在容量满了以后,自动创建一个新的数据库,避免在一个数据库中积累过多的瑕疵,是这个意思吧?这个想法看起来不错,这样一来数据库用起来方便、放心。
版主我不太相信你说的,要是数据库发生改动那升级所有的库就会是一个很复杂而且很费时的一个过程,如果通用产品使用这种结构,那维护成本不是高得吓人吗?
请版主详细说明一下,谢谢!
应该设定一个数据库版本号,版本号存放到一个生成器中,程序的功能集运行之前应该先查询数据库版本号。另外,数据库每当做一次更改,就把相应的ddl记录下来,放入程序。当程序检查版本号是老版本,那么就执行该DDL。或者也可开发一个累积性数据库升级工具,根据不同的数据库版本执行相应的升级ddl语句。
版主,实不相瞒,我们公司现在做的进销存产品,遇到了一个严重的问题,我们有一个客户使用了我们的产品6年,现在数据库长到了20G,现在作任何操作都比较慢,但是原来数据库里某产品的库存,客户的往来帐款,基本所有的实时数据都是统计原始数据来生成的,客户也不愿意投资升级硬件,看了版主的这个贴子后,我有了一些想法,但是又不知道如何下手,版主有没有一些例子能看一下呢?
你的情况是典型的流水库机制。不过单纯为了提高性能,不一定用流水库。可以用“超前计算法”,在触发器里面直接动态计算统计结果,保存到辅助表中,相当于数据一录入,就已经把结果算出,查询的时候,直接取中间表的值即可。他的原理就是,把计算任务直接分摊到录入的时候。例子没有太合适的。你自己试试看。我记得我的一些帖子曾详细的描述了这种方法,你仔细找找。
如果用流水库,配合中间辅助表,那么系统就会更健壮,性能更好。因为按照业务区间分库的话,统计表的查询条件都可以不要了,直接针对当前业务区间。
高手救救我,我按照你的说法做了一个例子,果然很强,但是我想到一个问题,我每次插入数据重新计算临时库中的数据,这些操作都是事务级别的操作,而且我们的客户都是做批发的,每次销售产品都是一箩筐一箩筐的,终端也很多,会不会造成数据插入上的瓶颈,高手你能不能加我QQ
没有什么瓶颈。但是这种情况下,还是我曾经说的那个原则,那就是,使Transaction尽量短促,是短小生命周期的Transaction。如果是IB/FB数据库的话,发生锁定冲突的时候,会抛出异常,在程序设计的时候,一定要应对这种异常,提供N次重试机制,N可设定。要把太大的Transaction切割成为小的Transaction。我有MSN: clxbase@hotmail.com
谢谢了,以后还得多多向您请教!