FB/IB多代事务结构详解--对FB事务最好的讲解


近来,接到很多人询问InterBase的事务处理问题。我在以前文章的基础上重新加以整理,写了这个说明InterBase事物处理的短文,希望对大家有所帮助。此外,我希望唐版主能把这个短文做成一个单独文件连接,便于大家查阅,谢谢。


InterBase的事务管理

我 们知道,与其它关系数据库系统不同,InterBase采用独特的多代结构和版本事务来提高其性能,因此,对InterBase来说,事务就显得尤其重 要。为保证其版本事务的工作,InterBase要求任何一个对数据库的访问都必须包含在一个事务中进行,也就是说,无论对数据库进行读select、插 入insert、更改update还是删除delete都要先启动一个事务,然后进行操作,操作完毕,结束事务。你不可能在没有事务的情况下对数据库进行 操作。有的关系数据库系统可能在读操作下并不启动事务,只有在改写情况下才启动事务,然而InterBase却不同,你对此必须有清醒地认识。
我们日常编程,通常使用已经编写好的控件,这些控件如IBX、DBExpress已经包含了事务处理的代码,但是在底层,归根结底都是通过InterBase的API函数进行的。
在InterBas的API函数中,事务处理常用的API函数有:
1、isc_start_transaction():使用已经定义的TPB(事务参数缓存)在一个或多个数据库上启动一个新的事务。
2、isc_commit_retaining():提交事务的更改,但并不结束事务的执行,仍然保留事务的上下文,可以再使用。
3、isc_commit_transaction():提交事务的更改,并结束事务的执行,释放上下文。
4、isc_rollback_transaction():回退事务的更改,并结束事务的执行,释放上下文。
启动一个事务分为三个步骤:创建并初始化事务句柄,可选择的创建并配置事务参数缓存,最后调用isc_start_transaction()。处理事务的永恒不变的原则是:任何事务都要尽可能地及时结束,不要拖延。
这 里我想强调一点,那就是InterBase的事务启动方式。通常情况下,对程序员和用户来说InterBase事务有两种启动方式,隐含启动和显式启动。 大多数情况下大多数的程序员采用的是隐含启动,也就是你并没有明确调用(直接或间接)事务处理函数来处理事务,此时则由InterBase自动为你做这些 工作。然而采用显示的事务处理应该说是一种好的、值得推荐的方法,这可以有效地配合InterBase的多代结构工作。当然显示事务启动需要自行调用(直 接或间接)事务处理的函数,就得多写几行代码。
有些人不理解,既然InterBase能在你不显式处理事务的情况下自动帮你处理,那还要我自己处 理事务干什么?答案是:机器永远没有人聪明!它有时不知道应该及时结束一个事务!尤其在我们使用像IBX、DBExpress这样的控件的时候,它们对事 务的自动处理并不是尽善尽美的。
下面我们来了解一下事务参数缓存TPB。TPB对事务的执行有着至关重要的影响。甚至可以毫不夸张的说,搞懂了 TPB,也就搞懂了InterBase事务处理。然而,我们经常使用的IBX、DBExpress等组件在底层封装了事务处理API函数,让很多人不能明 显地(至少可以说不能像了解其他属性那样)了解TPB的含义。
TPB参数可分为如下几类:
1、事务版本号:isc_tpb_version3,InterBase引擎内部使用,表明事务的版本号。必须是TPB的第一个参数。
2、访问模式:指明事务可以对数据表进行的操作。有isc_tpb_read、isc_tpb_write两个选项。
1)isc_tpb_write:允许对表进行读写(select,insert,update,delete)操作,为缺省的访问模式。
2)isc_tpb_read:只允许对表进行读(select)操作。
只能设定一种访问模式,否则后者将覆盖前者。如果不指定,则使用缺省值。
3、事务之间的隔离级别:表明并发的事务间相互隔离的程度,也可以说是相互不受影响的程度。注意这里强调的是并发的事务。如果不是并发事务,则相互之间不会存在冲突,也就不需要进行相互间的隔离。事务的隔离级别通常有三级,分别是:
·read dirty脏读
·read committed提交读
·consistency一致性
read dirty脏读是最低的隔离级别,它允许一个事务访问另一个并发事务所作的还没有提交的更改。由于这个隔离级别可以读到其他事务未提交的数据或者不完整的 数据,因此可能造成数据的不一致性,所以在关系型数据库系统中通常不被推荐。虽然从理论上讲InterBase的多代结构可以支持该选项,但实际上 InterBase并不接受脏读,因此实际中不要使用这个选项。BDE支持read dirty。
因此InterBase只有有下列四个隔离选项:
isc_tpb_read_committed, isc_tpb_rec_version
isc_tpb_read_committed, isc_tpb_no_rec_version
isc_tpb_concurrency
isc_tpb_consistency
实际是三个隔离级别,由低到高分别是:
isc_tpb_read_committed
isc_tpb_concurrency
isc_tpb_consistency
显然,隔离级别越高,并发性能越低。
1)isc_tpb_read_commited: 这是InterBase中支持的最低隔离级别。它只允许一个事务访问其他并发事务已经提交的更改,没有提交的更改即脏数据则不允许读取。该选项可以充分发 挥InterBase的多代结构优势,提供高吞吐、高并发的性能。该隔离级别必须和其他两个选项isc_tpb_rec_version、 isc_tpb_no_rec_version之一配合使用。它们提供了对已提交更改更精细的访问控制。
·isc_tpb_read_commited,isc_tpb_no_rec_version 是缺省的调优选项,表示事务只能读取数据行的最新版本,如果该数据行的某个更改处于挂起状态即还没有提交,则不能被读取。也就是说如果数据行的所有版本已 全部提交,则该选项可以访问到已经提交的最新版本,但如果数据行还有未提交的版本,则不允许访问该数据行,就连已提交的版本也不能读取。如果使用 isc_tpb_wait选项,那么此时事务只能等待其他事务提交或者回退这些未提交的版本数据;如果使用isc_tpb_nowait选项,那么此时事 务不会等待而是立即返回一个冲突错误。
· isc_tpb_read_commited,isc_tpb_rec_version则表示事务可以立即读取已提交的数据行最新版本,尽管该数据行最近 还有多个挂起的未提交版本。也就是说,不管数据行是否存在未提交的版本,该选项总是可以读到已提交的最新版本。这种情况下可能会产生冲突,要小心使用。
isc_tpb_read_commited选项特别适合用于和用户交互的在线事务处理系统。
2)isc_tpb_concurrency: 这是比read committed高一级的隔离,相当于Snapshot,在BDE中又叫作重复读Repeatable Read。 它允许一个事务获得该事务启动时数据库的一个快照,此后,它只能看到自身对数据库的更改,而其他并发事务的任何更改它均无法看到(这意味着这种隔离级别并 不限制其他并发事务对数据库的改写,只是这些改写看不到罢了)。由于要保持事务启动时数据库的一个快照,所以事务启动时数据库中记录的所有版本必须也保持 不变,这就必须禁止垃圾收集的运行。可见这个选项很适合于报表应用程序,因为无论重复几次读取表中的数据它总能获得相同的数值。和其他关系数据库系统不 同,这个选项既提供了良好的重复读功能(较长的读事务),又不影响其他用户事务的并发在线操作(如更改),充分发挥了InterBase的多代结构优势, 提供高吞吐、高并发的性能。该选项不大被推荐用来和用户进行交互,因为用户往往需要的是实时的数据而不是好像“冻住”了的数据。此外使用该选项的事务一定 要尽快结束,不能拖很长时间。
3)isc_tpb_consistency:这是最严格的隔离级别,相当于强制重复读Forced Repeatable Read,这也是InterBase特有的隔离级别,其他关系数据库系统和BDE并不支持这种级别。在这种隔离模式下,如果一个表正被一个事务访问,则不 允许其他任何事务再对其进行改写,即相当于在表上加了一把写锁。该选项一定要小心使用,因为它会极大地降低并发性能,并且一旦使用则一定要在操作完成后尽 快结束事务。
隔离级别也只能设定一种,否则后者将覆盖前者。
不隔离级别的事务之间的交互如下表:

各种事务隔离级别的对比如下表:

API level constants & IBO. Language level & tools. BDE level.
(Not supported) (Not supported) Dirty Read
Read Committed Read Committed Read Committed
Concurrency Snapshot Repeatable Read
Consistency Snapshot table stability (Not supported)
4、锁冲突的解决方案:表明当一个事务在写操作(更该、删除)期间遇到访问冲突时将如何处理。
isc_tpb_wait是缺省的方式,表示事务必须要等待,直到其他并发事务结束,锁定的资源被释放。一旦资源被释放,事务就重新尝试其操作。
isc_tpb_nowait则表示事务只是返回一个冲突错误而不等待资源释放,故此时事务不会再作尝试。
这两种方式也只能指定其中一种,否则后者将覆盖前者。
5、 表的保留方式:通常,事务只有在真正从数据表中读无数据或往表中写入数据时才获得特定的访问权。表的保留选项则表明了对事务访问的特定表的访问模式和锁的 解决方式。当这个选项被使用的时候,那么当事务启动时,这个表就被保留着某种特定的访问权限,而不是在表实际被访问时。该选项在多个并发事务共享数据库访 问的环境中才有用,它有如下三个主要目的:
·防止通常情况下的死锁和更该冲突。
·由于触发器和约束可能影响表的锁,因此该选项可以提供依赖锁。尽管没有必要提供一个明显的依赖锁,但它能确保由于间接的表冲突而不产生更该冲突。
·可以在事务中改变对一个或多个单独的表的访问级别。
有效的保留方式有:
1)isc_tpb_shared ,isc_tpb_lock_write:允许使用isc_tpb_write访问模式+ isc_tpb_concurrency或+isc_tpb_read_commited隔离级别的任何事务进行更改,而使用上述隔离级 别+isc_tpb_read的事务可以读取数据。
2)isc_tpb_shared ,isc_tpb_lock_read:可以允许任何事务读取数据,也允许使用isc_tpb_write访问模式的事务更改数据。这是最自由的保留模式。
3)isc_tpb_protected,isc_tpb_lock_write:阻止其他事务更改。使用isc_tpb_concurrency或isc_tpb_read_commited隔离级别的可以读取数据,同时也只有这些事务可以更改数据。
4)isc_tpb_protected,isc_tpb_lock_read:禁止所有的事务更改数据,同时允许所有的事务读取数据。
TPB的格式如下:
版本号
访问模式
隔离级别
冲突解决
表保留选项
例如1:
isc_tpb_version3,
isc_tpb_write,
isc_tpb_concurrency,
isc_tpb_nowait,
isc_tpb_protected, isc_tpb_lock_read, "EMPLOYEE"
例如2:
isc_tpb_version3,
isc_tpb_write,
isc_tpb_concurrency,
isc_tpb_nowait,
isc_tpb_protected, isc_tpb_lock_read, "COUNTRY",
isc_tpb_protected, isc_tpb_lock_write, "EMPLOYEE"
缺省的TPB是:isc_tpb_version3,isc_tpb_write,isc_tpb_concurrency,isc_tpb_wait。
通过上面的说明,我们对InterBase的事务处理有了一定的理解。那么这对我们实际的应用系统有什么参考价值呢?在此我给出一点看法,正确与否仅供大家参考。
1A、在线事务处理应用系统:这些系统的显著特点是大量并发用户与数据库交互,用户基本都要对数据库进行频繁的改写。建议使用如下的TPB参数:
isc_tpb_version3
isc_tpb_read_committed
isc_tpb_rec_version或isc_tpb_no_rec_version,尽量用前者
isc_tpb_nowait
2B、混合应用系统:这些系统的特点是既允许用户对数据库交互,又需要进行报表或数据决策,但侧重于报表和数据决策。建议使用的TPB参数:
isc_tpb_version3
isc_tpb_concurrency
isc_tpb_nowait
3、报表数据决策应用系统:该系统的主要目的是进行报表处理或数据决策支持。这里边又可分为两类:
3C一类是不允许任何事务对数据库进行改写(只读),纯粹的报表和数据决策,建议使用的TPB参数:
isc_tpb_version3
isc_tpb_read
isc_tpb_consistency
3D另一类是只允许当前活动事务对数据库改写而不允许其他并发事务改写(排他写),建议使用的TPB参数:
isc_tpb_version3
isc_tpb_consistency
最后然我们看一下IBX、DBExpress、DataSnap对InterBase的事务处理。在这里我之所以没有讨论BDE,是因为BDE对InterBase的事务支持并不是很好,目前不大被推荐作为访问InterBase的技术。
1、IBX
IBX 使用IBTransaction控件进行事务控制。大多数人已经了解了如何设置IBTransaction的属性来让其工作,我不再赘述。但是在 IBTransaction中如何设置TPB来让它满足我们系统的需要呢?这就需要IBTransaction控件的params属性了,我们可以在这个 属性改变TPB参数,以此来改变对事务的控制。默认情况下如果你让IBTransaction控件的params属性为空,则相当于 isc_tpb_version3、
isc_tpb_concurrency、isc_tpb_wait,这可能并不是你想要的。鼠标右键点击IBTransaction控件,就会出现一个事务编辑器,共有四个选项。各选项意义与我们前面介绍的对应如下:
snapshot:相当于2B。
Read Committed:相当于1A。
Read-Only Table Stability:相当于3C。
Read-Write Table Stability:相当于3D。
如果这些选项不符合你的需要,你就要点击params属性自己编辑了。
2、DBexpress
DBexpress 中使用TSQLConnection控制事务。启动事务的语句是:
procedure StartTransaction(TransDesc: TTransactionDesc);需要一个事务描述类型作为参数。事务描述类型如下:
type
TTransIsolationLevel = (xilDIRTYREAD, xilREADCOMMITTED, xilREPEATABLEREAD, xilCUSTOM);
TTransactionDesc = packed record

TransactionID : LongWord;
GlobalID : LongWord;
IsolationLevel : TTransIsolationLevel;
CustomIsolation : LongWord;
end;
我们看一下其事务隔离级别:
xilDIRTYREAD:脏读,允许当前事务看到其他事务所作的任何更改,即便这些更改还没有被提交。
xilREADCOMMITTED:读提交,即允许当前事务只看到其他事务已提交的更改。但是如果在事务结束前有额外的更改提交,将得到不一致的数据视图。
xilREPEATABLEREAD:确保当前事务得到一致的数据视图。因此它仅仅允许该事务看到在该事务启动时已经提交的更改。
xilCUSTOM :表示当前事务使用数据库特定的隔离级别,由CustomIsolation指定。遗憾的是xilCUSTOM选项目前没有支持。
通 过对比可见,DBExpress的事务隔离中,xilREADCOMMITTED相当于InterBase的 isc_tpb_read_commited+isc_tpb_no_rec_version,xilREPEATABLEREAD相当于 isc_tpb_consistency。当然,如果TSqlconnection能提供xilCUSTOM选项就更好了,这样便于结合数据库类型更好的 调整事务处理的方式。
3、Datasnap
DataSnap处理事务的模式在李维先生的书中有很好的描述。我们关键是要记住一点,当需要 对数据库进行改写即调用ApplyUpdats时,DataSnap会自动启动事务为你工作,这也是默认的方式,可能很多人就直接使用这种默认方式。可是 我要强调一点,在这种默认方式下,DataSnap总是在当使用Tclientdataset时才进行事务处理,如果没有使用 Tclientdataset,DataSnap就不会自动进行处理事务。因此,如果中间层中你的数据集通过datasetProvider输出给 Clientdataset,就不会有什么问题嗯,但是那些没有通过datasetProvider输出到Clientdataset的数据集,就不会在 datasnap的事务处理控制下,即便该数据集仅仅是读操作,也需要嵌套在事务中进行,这是Interbase必须要求的。所以我的看法是,不论何时, 还是使用显式事务控制好。我们可以在中间层中,对于对数据库的每一个操作都显式地调用事务处理语句,并及时结束事务。这个做法对两层结构也同样适用。
如 果你没有使用显式事务控制,对于在中间层中使用IBX,要进行一些特别的设置,如:如果数据集准备通过DatasetProvider输出给 Clientdataset, 要设置数据集关联的TIBTranSaction的AutoStopAction为saCommit,那些不通过DatasetProvider输出给 Clientdataset的数据集,其关联的TIBTranSaction的AutoStopAction要设置为saNone。此外,在中间层中使用 IBX,还要把数据集的UniDirectional属性设置为true,使之成为单向的,因为我们不需要中间层为我们缓存数据,而是通过客户端的 Clientdataset缓存数据。
最后,我还想说明一点,有很多人询问在Interbase中如果对一条记录加锁。首先我认为这些人很是受其 它关系数据库系统模式的影响,没有真正理解InterBase的多代结构的工作原理。实际上在InterBase的多代结构中,没有必要对一条记录加锁, 这也是一种不正常的做法。因此,从其它关系数据库转来使用InterBase的程序员,要转变这个观念,把心思更好的用在理解把握InterBase的版 本事务模式和多代结构上。
posted @ 2009-10-26 12:21  与时俱进  阅读(887)  评论(0编辑  收藏  举报
友情链接:同里老宅院民居客栈