优化 构架高性能的InterBaseFireBird系统.docx
《优化 构架高性能的InterBaseFireBird系统.docx》由会员分享,可在线阅读,更多相关《优化 构架高性能的InterBaseFireBird系统.docx(12页珍藏版)》请在冰豆网上搜索。
![优化 构架高性能的InterBaseFireBird系统.docx](https://file1.bdocx.com/fileroot1/2023-1/21/c8b6a56d-089a-4983-8066-08977eeb4cb4/c8b6a56d-089a-4983-8066-08977eeb4cb41.gif)
优化构架高性能的InterBaseFireBird系统
构架高性能的InterBase/FireBird系统
日期:
2007-04-23|分类:
数据库
版权声明:
转载时请以超链接形式标明文章原始出处和作者信息及本声明
有以下几个诀窍:
1、业务主键和数据库物理主键分离
在抽象系统业务模型时,我们都习惯于根据业务的情况定义出一个表的唯一不重复的关键字(主键)。
然而,更好的做法,是不论具体业务如何,把所有的表的主键都定义成Generator支持下的自增长整数,作为记录编号(命名为xxx_key),把“业务主键”定义为不可重复的索引(甚至于根本不用定义,如果没有特殊的检索要求的话)。
在这种主键设计策略中,隐含着一个很深的道理,那就是“将业务逻辑和物理逻辑脱离开”的原则,实体的业务逻辑并不直接运用到真实的物理逻辑实现上。
以单据表作为一个典型的例子,单据往往具备着一个不可重复的“单据编号”,一般它的数据类型是一个几十字节的字符串,它相当于是“单据”这个实体在业务逻辑上体现出来的主键。
而在这里的规范中,却并不以这个字符串编号为真正物理上的主键,而是采用一个自增长的KEY取代它成为真正的物理上的主键。
以此为基础,表与表之间的关联也同样以一个单独整数字段进行关联,这个字段对于主表,一般就是主表的主键字段。
在外键的创建中,一般也尽量避免多字段关联的联合外键。
在FireBird数据库中,这种设计方式利用了FireBird特有的生成器(Generator)机制来实现。
在创建外键时,一般都将外键的cascadedelete打开,利用数据库的级联删除功能达到了自动删除从表记录。
2、索引的建立
要为可能频繁发生的检索的条件组合建立相应的索引。
如果定义了主从表的外键关联,那么系统会自动创建所需的索引。
但针对不同的数据类型,索引的执行效率也是不同的。
对于复杂的查询,可以通过plan子句告诉服务器采用怎样的索引进行数据检索,格式如下:
Select语句
PLAN(
表名1 INDEX(索引名称),
表名2 INDEX(索引名称)
)
3、自增长字段的建立
建立表的时候,在IBExpert中通过打开字段的AutoInc属性设定某字段是自增长字段后,同时建立相应的生成器和触发器;通过调用SQL语句来得到服务器端生成器的值并使之加1:
Selectgen_id(AGenerator,1)asidfromrdb$database用一个ibquery控件来执行该语句产生永不重复整数序列。
一般情况下,一个表对应着一个生成器。
但是在某些情况下,可能多个表共用一个生成器,也可能一个表用多个生成器
4、应尽量避免字符串字段作为关联查询的关联条件。
5、应尽量避免多重字段、字符串字段(特别是常字符串)作为主键、关联查询的关联字段
6、在系统构架方面应该采取划分业务数据生存期的方式,在系统中采用动态创建数据库的机制,使不同的业务区间对应一个单独的数据库文件,比如说,一个会计核算年对应一个GDB文件,到年底核算时,为下年创建一个新的数据库,新数据库中的常量数据从老数据库中继承过来,而动态的业务数据从0开始。
划分动态数据库分区的原则就是,把每个数据库文件大约控制在600M左右(更大些也可以,但不太利于备份刻录到光盘上)
看了你的经验收益良多.
你在里边提到:
在SQL语句不是很多的情况下,应避免在代码中书写动态SQL语句,特别是对于把数据显示界面相关联的Select语句更不推荐使用动态SQL,而是要将不同的语句放在不同的Query元件中,而且将查询控件的字段实体化
====================================================
这样的话只要用良用到条件查询,就要切换DataSource相关的数据集组件,而且代码中也要用DataSource.DataSet的方式引用数据集来进行同步数据集操作.
TIME:
2006-12-214:
05:
31 IP:
Protected
.NET时代的win32英雄
广泛的使用自增长整数作为主键,是不是会在大批量数据的导入时遇到很大的麻烦?
如果两表关联还是用的业务主键相关联那么逻辑主键似乎没有存在的必要
逻辑主键存在的必要就是提供检索
==============================================
两表用逻辑主键相关联这样的确很大程度的精简了两表关联的SQL处理
可是会不会造成两表关联的不正确呢?
说的非常好。
在数据导入过程中,尤其是从逻辑主键的设计升级到物理主键的设计时,两表的关联确实需要一个将逻辑主键关联转化为物理主键关联的过程。
处理这种做法,我有两个方法。
其一就是客户端处理,方法是在客户端放一个内存表,预先载入被关联的基础数据表,其中既包含物理主键,又包含逻辑主键(别的字段可以省略),然后进行关联表的导入,导入时,每插入一条记录之前,都通过内存表查一下逻辑主键对应的物理主键的值,把该值放入记录相应的关联字段中,再插入数据库。
第二就是服务器端处理。
是在服务器端的触发器中判断如果整数关联字段为空,那么自动通过原始的逻辑主键关联字段查出物理关联字段对应的值,赋值进去。
意思差不多。
如果是系统本身就采用了这种物理主键的思想进行设计的情况,那么一切都OK,只不过在数据导入导出过程中需要注意两个问题:
1、尊重已形成的自增长主键的值,也就是,已形成的自增长主键的值不能让它重新生成,是多少就是多少,原样导出,原样倒入,这样就不会错乱。
IBExpert生成的触发器因为有判断语句,所以并不会干扰。
2、生成器对象的值也要随着变。
特别是,在空数据库倒入历史数据时,一定要把生成器的值也继承过来。
注意了这两点,你就能对我提出的“业务主键和数据库物理主键分离”的思想运用自如,设计出的系统也有很好的性能。
我们公司的会计软件就是这样做的。
一个主数据库,一个模板数据库,然后是一个套帐一个目录,一年一个数据库。
但是,要跨年度查询时,需要同时或逐一连接多个数据库,
备份一个套帐的数据时也要将多个数据库备份并打包。
跨套帐汇总时,更是要逐一连接多个数据库(多的时候有70个套帐)。
一个数据库一年的数据大小为5-10MB。
==============================================
这就是我所提倡的“流水库”机制。
这种机制的要点在于,根据业务在时间上的运转周期,将业务划分为一个一个的业务周期,每一个业务周期创建一个新的数据库,以一个个单独的数据库作为数据在业务时间周期的天然分区隔断。
数据库分区的划分原则就是使跨数据库查询的操作尽量少,同时兼顾单个数据库数据的大小不要超过700M,最好是一张光盘能容纳的范围。
每当系统到达业务时间分区的临界点时,系统将新建一个数据库,新数据库把常量数据从当前数据库中继承过来,当前数据库封顶,新数据库启动。
业务分区最常见的就是按照财务年、或财务月分区,各有优点。
这样进行系统设计带来了两个巨大的好处。
其一就是增加了系统的健壮性。
数据在不断的产生中不断的形成新数据库,这样单个数据库不会无限增大,同时也不容易集聚瑕疵,不易崩溃;其二,就是提升了系统的性能。
由于久远的历史数据都位于不同的历史业务库中,所以当前活跃的业务库内就没有大量的历史陈旧数据,系统在进行数据检索和查询的时候,直接面对的就是与当前业务直接相关的数据,所以,系统的运行永远象刚投入使用不久那么快。
但如果必须需要进行跨库查询的话,那么通过一定的算法也能够解决这个问题。
我本人曾经提出过“数据集合并法”专门解决跨N个数据库查询的问题。
在采取了强力的优化之后,跨数据库查询的性能也不错。
TIME:
2007-2-1318:
28:
21 IP:
Protected
可以请yangdong兄介绍一下“数据集合并法”吗?
=========================================
其实是用内存表的方法进行数据集记录的合并,原理并不复杂,就是通过循环,在每个数据库中进行某查询,把结果通过一定的规则添加到内存表中。
比如说,
1、最简单的合并就是数据集相加,就是用循环把每个数据库的查询结果都放到一个内存表中
2、求和(sum)合并,就是每个查询结构插入内存表中时,先按照主键判断是否已存在记录,如存在,则把特定字段的值加到已有记录的相应字段中(求最大值、最小值道理相同)。
3、平均值合并,需要绕道处理,对总量和总数量分别求和,然后作除法。
第三种情况几乎没有多少实际应用。
第一种情况是简单的追加。
第二种情况往往是最典型的场景。
方法的关键在于
1、性能:
怎样能使跨库查询的速度不低于在同一个数据库中的查询速度?
2、巧妙的封装,使程序员编写跨库查询语句的时候,几乎都不用管切换数据库的细节,不必写过多的冗余代码处理复杂的数据集合并的功能?
对于性能而言,数据集合并的关键在于记录的定位算法要非常快。
目前最常用的方法就是使目标数据有一个具有不重复的单个整数字段的主键,暂且称之为rec_key(这也是我热衷于使用自增字段的原因之一),内存表在使用之前需要创建一个针对rec_key的索引。
在第二种情况中,在插入记录的时候先对内存表进行Locate,定位成功,则进行字段的相加修改,失败,则把记录追加到内存表中。
一般情况,具有单整数字段的索引的性能已经相当的好了,这种情况下,巨大的数据集的合并也有很不错的性能。
(然而,有的时候我们如果追求极端的速度,则有可能编写针对数据集的Helper数据结构,来获得比Locate函数更加迅速的定位算法,甚至于根本不用内存表,直接用动态结构数组来盛放数据,用自定义的定位方法来定位数据,这就是后话了,一般根本用不着),如果你的记录集根本没有单整数的主键,只有很长的字符串的主键,或者是好几个字段的联合主键,那这个方法就有相当的效率折损。
当然创建索引仍然有巨大的帮助。
在封装性方面,一定要达到把跨库查询机制化,使各种各样的跨库查询都能够很方便的进行,而不是每个查询都得写一大堆代码去进行多数据库循环。
那么就需要把跨库查询机制固化到公用DataModule中,成为一种共有功能,把这个功能封装成为一个函数,包含有三部分参数:
一个ibQuery控件(或者SQL语句),一个时间范围(该时间范围跨多个数据库),数据集的主键字段名称,该函数在执行中就首先根据SQL把内存表的字段都创建出来,然后创建索引,然后根据时间区域计算出所跨的数据库列表,然后在循环中连接每个数据库,执行SQL语句,把记录集合并到内存表中,最终,内存表中的数据就是成果物。
这种方法下,新增一个跨库查询就用不着写一堆代码了。
这里需要提及的就是IBQuery控件的UniDirectional属性应该设定为True,使之成为单向数据集,性能会更好。
在这个机制中,可以利用ibquery的Field对象的Tag来约定告诉跨库查询函数该字段是需要求和呢,还是求最大值,或者是不做任何操作。
TIME:
2007-2-1318:
29:
16 IP:
Protected
某位仁兄的疑问:
对于采用流水库,我有一个疑问,那就是在建立索引的情况下,查找第一个记录和最后一个记录的时间是一样的,不管其数据量多么庞大,因为记录指针的表示范围是不变的,指针驱动磁盘走向到某个扇区去读取数据,只要不是跨设备读取,就不会什么效率问题出现。
因此,采用流水库是不是一个多余的做法?
说到系统效率慢的问题,我在具有上百万条记录的数据库中读取一个只有几十条记录的表,发现效率并没有受影响,而在读取具有庞大数据量的表时,如果检索不当,会出现网络崩溃的现象,而如果我只运行selecttop10*fromtable这样的语句时,并没有受到效率的影响,可想而知,数据库的运行效率并不是记录的增长或者是数据库容量的变大而出现效率低下。
那么是什么原因导致大数据量而效率低下的问题呢?
那应该是设计的缺陷。
有以下几点:
第一:
系统分析不够。
导致不清楚需求,大量采用如同select*fromxxx的语句进行检索,不加业务条件或对业务条件理解不够。
第二:
数据库设计没有效率。
在业务需求不清楚的情况下,如果数据库设计还不够稳健,那可是件悲惨的事情,没有效率的数据库在数据量小的情况下一般难以表现出来,而到量级情况下,问题暴露无疑。
这不能说是数据库管理系统的问题,而是设计者并没有按照数据库设计规范来做。
数据库不是一个简单的记录库,我们应该认真研究数据库的设计思想后再做效率优化,不能简单的设计一个表后就算完了。
比如业务数据表,我们将企业发生的所有业务都存储与该表内,那么这张表必定会涉及到很多的外键关联,而且数据量也会变的惊人。
而我们可以从容应对,因为根据会计和实际需求,我们并不会去把十年到今天的帐一并汇总,因此不会出现大量数据汇总的情况(当然,有人要这么做的话,那也是说明他有问题了,但顶多失去的是十倍的查询时间罢了),那么我们就没有必要担心上述情况发生。
根据业务实际需求,查询汇总总归是以某段时间内发生的业务统计,因此,为业务时间字段建立索引是必须的。
如果业务还需要对某列进行分类统计,那么该列上也要建立索引。
建立索引的作用可以参考数据库开发手册,具体数据库引擎怎么使用索引,那我们没有必要去了解,只要知道,要查询的字段,如果是数量级的表,那就必须建立索引,否则等死。
说了这么多,其实就是一个,为必要的查询字段建立索引,不要去想着去做什么万能查询,那是程序员偷懒不动脑筋的做法,而且用户对这个功能并不会给多少的掌声,还不如按用户的实际需求,做些实实在在的查询来。
TIME:
2007-2-1318:
32:
54 IP:
Protected
对于采用流水库,我有一个疑问,那就是在建立索引的情况下,查找第一个记录和最后一个记录的时间是一样的,不管其数据量多么庞大,因为记录指针的表示范围是不变的,指针驱动磁盘走向到某个扇区去读取数据,只要不是跨设备读取,就不会什么效率问题出现。
因此,采用流水库是不是一个多余的做法?
===============================================================
理论上讲是这样的。
然而实践中,开发人员却会遇到各种恶劣的环境,超出普通的预料。
记得我曾经带领徒弟编写过FireBird数据库的智能卡售饭程序,结果系统被卖到了某农村的中学里面去了,后来得知那个地方不定期停电,且有大功率设备引起的电磁干扰,服务器是低价的赛扬组装机,每次卖饭,将在20分钟内插入2000条数据。
过了几个月,那里的FireBird数据库发生了Crash,里面有很多坏页面。
在这种限额的条件下,我们发现数据库的强健性是有一定限度的,不是象想象的那么强壮。
流水库的想法也是在那段时间产生的。
倘若那时采用了这种分库机制,我敢说即使在这种环境下,该系统用几年也不容易出问题。
说到系统效率慢的问题,我在具有上百万条记录的数据库中读取一个只有几十条记录的表,发现效率并没有受影响,而在读取具有庞大数据量的表时,如果检索不当,会出现网络崩溃的现象,而如果我只运行selecttop10*fromtable这样的语句时,并没有受到效率的影响,可想而知,数据库的运行效率并不是记录的增长或者是数据库容量的变大而出现效率低下。
================================================================
理论是这样。
然而,我如果想按照业务时期对数据进行备份、删除掉,那么流水库不是非常的方便吗?
特别是数据的删除更是如此。
其实,这其中有很多的设计上的艺术性。
TIME:
2007-2-1318:
50:
35 IP:
Protected
数据库的崩溃是否与数据库容量密切相关?
在其他问题(频繁停电、、电磁干扰和频繁使用等)得不到解决的情况下,一个月或者更短时间建立一个数据库即可解决问题?
========================================================================
动态建库是一个理念变化,也就是说,“系统不依赖于一个固定的永久性库”。
动态建库同时能够分化数据风险,将风险分散到数据的子集上。
数据库物理存储的瑕疵是有积累效果的。
在极限情况下,FireBird数据库对数据库文件的瑕疵产生有一定的容忍能力。
我的经验是,具有2、3个簇错误的gdb文件基本上仍能够继续使用,这样,当系统进入新业务时间的时候,数据库瑕疵就被抛弃在老库中了。
这样,系统即使不进行修补、备份、恢复,仍能很好的运行,这样,真正就能够实现“无人值守”的数据库系统了。
如果是单个数据库,那么数据库文件错误必须被及时发现、修补,否则会导致整个系统的崩溃,乃至于所有的数据都损失掉。
每次卖饭,将在20分钟内插入2000条数据。
几个月可以增加多大的数据库容量?
========================================================================
数据量并不算大,一个月只有几十M的数据量。
不过,我却回想起另一个Case来。
有一次,公司里的车牌识别系统在现场出现致命的性能问题,症状是,插入数据奇慢无比,老的数据无法删除,删除速度小于插入速度,系统濒临崩溃的边缘,而且此前系统曾经发生过两次数据库Crash,所有数据损失。
公司内部召集几位专家进行情况分析,我也过去看了。
结果发现,系统设计极不合理:
该系统采用sqlserver,关键部分是两个自关联表,关联字段是guid类型的字段,车牌识别图片保存在Blob中,数据库目前已经达到了200G,数据库执行SQL滚动删除来删除老数据。
后来,我提出了若干改进意见:
1、数据保存在多个数据库中,每个数据采集点采用单独的库,取缔中央数据库;
2、采用自增长整数作为关键字和关联字段,并建立索引;
3、图片不要保存在数据库中,而是存在硬盘上;
4、采用按时间创建动态数据库的方法存储,定期直接删除整个数据库,而不是执行deletefrom语句
后来,有关人员听取了前两条,问题解决了。
这一点说明,不同的设计思路体现出来的系统的性能和稳定性会有天壤之别。
老天爷可不看你用的是不是名牌数据库。
用再好的数据库,如果设计不合理,老天爷很快会向你“发难”。
所以好的系统,它的构架设计太关键了。
TIME:
2007-2-1811:
29:
06 IP:
Protected
如果那个车牌系统换一个思路,采用我说的Firebird加上流水库的机制,数据库结构设计得当,定期滚动删除动态创建的gdb文件,而且同样是把图片保存到blob中,这样设计,我怀疑,系统用个十来年也出不了什么问题。
从那次经历,我也体验到,考验数据库平台的承受极限还真挺容易,不良的设计即便是对于SQLServer这样精良打造的数据库也能够致命。
不知道具体瑕疵是怎么出来的,如果定期备份后清空数据库,瑕疵就不会积累,数据资料也可妥善保存,分批保存的资料都可以调出使用。
===================================================
问题就在这里了。
如果要达到无人值守的程度,也就是说将数据库维护“傻瓜化”,在现场没有专门的数据管理员的情况下系统能够健壮的被使用,那么“定期备份后清空数据库”的动作就要求高了。
另外,按照时间建立新库的另一个着眼点就是性能方面,由于历史数据永远在以前的数据库中,所以单库的查询速度就完全不会因为历史数据的增加而受影响。
当然,按照“理论”来讲,如果建立了索引,那么数据量增加也不会按照正比的幅度降低系统性能,而分库的做法是完全消除了这种因素。
但是毕竟,动态建库的风格并不是为了某个特定的目地而提出的。
这种风格的设计带来的是一连串的因素的变化,这一点恐怕得在实际设计和运行中才能真正体会、理解到,其用意不是那么简单、具体。
概括的说,分库设计的目地,就是把数据库从“时间积累性因素”中脱出来。
“时间积累性因素”会在系统的生命周期中衍生出一系列的消极因素来,降低系统的性能和稳定性。
当然,你所说的类似的方法我还能进一步引申,那就是制作自动备份恢复数据库的服务,定时对数据库进行备份+恢复,每次备份恢复后,gdb文件肯定是很干净、紧缩的库结构,数据不丢失,这样看来,好像是“完美了”,然而,从设计的直觉艺术来看,总是给我感觉隐隐的有些不足,最大一点就是,仍然没有彻底的摆脱“时间积累性因素”的阴影。
打个不恰当的比方,就是,我感觉动态建库的思路设计的系统用500年问题不大,而用传统的单库设计我不敢这么保证。
当然,这是一个比方。
[该帖子由作者于2007年2月18日19:
45:
19最后编辑]
XX的数据库中存储了数十亿网页,查询的速度很快,而且我从没有感觉到它崩溃过,不知怎么弄的。
我解释一下你可能会感觉吃惊:
这些搜索引擎ISP使用的存储介质,其实根本不是常规意义的数据库,而是根据关键词的网络结构加上分布式文件存储,是ISP自己实现的存储格式,有点像神经元的原理。
从存储上,这些isp采用的分布式,而且存储内容的分布式布局都是为搜索而优化的;从检索索引上来看,搜索引擎索引体现的是特殊的网络状数据结构,并不是像我们所想象的放一个SQLServer,搜索的时候执行selectwhere contentlike'%xxx%',比这个复杂很多倍。
如果是靠数据库的搜索功能,恐怕10万页面就到秒级速度了。
中学食堂刷卡用的计算机不可能总是开的,通常情况应该是使用前打开,用完后关闭,关闭计算机前做些维护工作应该并不困难。
有些农村地区电压波动很大,瑕疵估计主要是由于电压波动和停电所致。
一些精密的仪器往往需要稳压电源,以前买计算机也要配稳压电源的,不应该随着计算机的价格下降就随便降低它的使用条件。
=========================================================
“关闭计算机前做些维护工作应该并不困难”,那是对咱们不困难。
可是某些用户却恨不得点一个按钮什么事情都干了,或者恨不得把机器打开,系统就能用,点一个按钮就出报表,剩下的他们什么都不管。
对于开发人员来说,备份数据库司空见惯。
然而对于某类用户来说,“做些维护工作”和“免维护”差别太大了。
当然,如果是大规模的外企,一般都有专门的DBA,那么你不给他找点事儿他反而无聊。
但如果不是这么实力雄厚的企业用户,可就不一定不在乎“做些维护工作”了。
当然,这是一个极端的比方。
该买各种设备的当然还得买。
只怕UPS、屏蔽罩买了,它又会出现其它意料外事情。
用户现场可能会出现各种开发人员意想不到的状况,会造成这类按理说永远不该发生的情况。
负责前台机器维护的是售货员或基层管理人员,而收集的数据需要送到财务部门和管理部门,这些数据将用来核查帐目、财务管理和监督。
前台收集来的数据不能随便改动,前台机器的操作必须非常简单,而且操作不能影响数据库中已有的内容。
维护数据库的工作由财务部门和管理部门负责,受过中等教育的人不难对数据库进行例行的备份工作,在前台机器与后台数据库之间可设一个开关。
==========================================================
你说的是理想状态。
在实际中,你的客户可能就一个主要部门,只有一个操作员兼财务主管。
通常这种规模的企业用户是无法划拨专门的数据库管理