首页> 中国专利> 在企业资源计划系统内的并发控制

在企业资源计划系统内的并发控制

摘要

涉及相同数据的多个数据事务之间的并发控制,包括比较唯一标识数据事务的读请求期间与写请求期间的数据版本的版本标识。如果版本标识不匹配则抛出异常,并且在数据事务内处理该异常。

著录项

  • 公开/公告号CN101405735A

    专利类型发明专利

  • 公开/公告日2009-04-08

    原文格式PDF

  • 申请/专利权人 微软公司;

    申请/专利号CN200780009422.3

  • 发明设计人 J·D·里奇;S·R·阿瓦达纳姆;Z·楚;

    申请日2007-01-16

  • 分类号G06F17/40(20060101);G06F17/00(20060101);

  • 代理机构31100 上海专利商标事务所有限公司;

  • 代理人顾嘉运

  • 地址 美国华盛顿州

  • 入库时间 2023-12-17 21:40:45

法律信息

  • 法律状态公告日

    法律状态信息

    法律状态

  • 2014-03-12

    未缴年费专利权终止 IPC(主分类):G06F17/40 授权公告日:20111214 终止日期:20130116 申请日:20070116

    专利权的终止

  • 2011-12-14

    授权

    授权

  • 2009-06-03

    实质审查的生效

    实质审查的生效

  • 2009-04-08

    公开

    公开

说明书

背景

在允许多个用户同时访问共享对象、数据记录等的系统内的并发控制是任 何用于管理共享数据项的基于服务器的产品的重要特征。具体地,在企业资源 计划系统内经常存在在具有长生存期的数据事务的用户之间支持不可串行化 的协作的需求。

通常,悲观并发控制不阻塞其它用户进行读操作。例如,可重复读确保被 读的行没有被更新,即不在数据事务的持续时间内更新。然而,在具有更新被 读的行的意图的读操作中,悲观并发控制在数据事务的持续时间内在数据项上 设置排它或更新锁,从而防止其它用户在具有更新意图的情况下读该数据项。 因此,其它用户在带有更新意图读数据项之前必须等待锁被解除,而这影响系 统的并发性和可伸缩性。在一些情形中,锁的范围应用于整个数据库、数据库 内的整个表或者表内的若干行,而不仅是包含正在读或更新的数据项的单个 行。因此,锁的范围防止多个用户同时读或更新不同行和/或表内的数据项。另 外,在平衡的树数据结构内,查询如SQL查询内不能在精确的位置处开始扫 描。作为查询执行的一部分,在评估该查询期间扫描行并应用过滤器。因此, 同时进行读的用户防止对方读这些数据项,即使它们的最终查询结果不交叉。 尽管应用程序可以选择行并且应用过滤器以基于过滤准则丢弃所选择的行,但 在这些所选行上获得的锁在该数据事务期间继续存在。因而,对于涉及诸共享 表的长生存期的数据事务,并发任务变为被串行化,即使在由查询得到的最终 集合内没有交叉的时候。

乐观并发控制允许用户读、更新和删除数据项而不防止其它用户做同样的 事情。乐观并发控制假设在写操作期间更新或删除同一数据项的概率很小,并 且读操作不受限制。然而,万一多个数据事务在写操作期间正在更新同一的数 据项,则更新会丢失并且在并发用户之间仅最后的更新被保存,从而引起数据 不一致。换言之,第一用户最终基于最初检索的值(但随后被并发用户改变) 更新表行内的数据项。因此,该更新是基于过时的数据的。

概述

涉及相同数据的多个数据事务之间的并发控制,提供一种在数据事务内处 理由并发控制产生的异常而非立即异常中止数据事务的方式。异常可以通过对 数据进行重读或重试更新来处理,从而延迟了数据事务异常中止。并发控制还 提供乐观并发控制和悲观并发控制之间的选择,同时考虑相对更新和表间依 赖。概括地说,在涉及来自应用程序的写请求的数据事务期间,将唯一标识要 被更新的数据的版本的版本标识与标识在同一数据事务期间先前读过该数据 时的数据版本的版本标识相比较。如果版本标识不匹配,则在该数据事务内抛 出异常并进行处理。预期使用并发控制技术来对数据事务去串行化,确保数据 一致,并且即使数据事务是长生存期也允许高可伸缩性。

附图简述

图1是计算机网络的简化和代表性框图;

图2是可连接到图1的网络的计算机的框图;

图3是用于管理并发控制的系统的代表性框图;

图4是表示乐观并发控制的例程的流程图;

图5是表示用于检测图4的乐观并发控制例程内的更新冲突的例程的流程 图;

图6是表示用于在图5的检测更新冲突例程期间抛出的结构化异常的例程 的流程图;

图7是表示用于在数据事务期间、在图5的检测更新冲突例程期间抛出异 常时处理异常的例程的流程图;

图8是表示用于进行数据的相对更新的例程的流程图;以及

图9是表示用于更新依赖于其它表中的数据的数据的例程的流程图。

详细描述

尽管下面的文本阐述众多不同实施例的详细描述,但应当理解,该描述的 法律范围是由所附权利要求书的文字来定义的。该详细描述应当解释为仅是示 例性的,而不是要描述每一可能的实施例,因为描述每一可能的实施例即使不 是不可能也是不实际的。可使用当前的技术或在此专利申请日之后开发的技术 来实现众多替换实施例,它们都落在本发明的范围之内。

还应当理解,除非术语在本专利中使用语句“如在此使用的,术语‘_______’ 在此定义为表示…”或相似语句来明确定义,否则不旨在以显式或隐式的方式 将该术语的含义限制为超出其简单或普通含义,且这样的术语不应当基于本专 利的任何部分中所作的任何陈述(除权利要求书的语言之外)来解释以限制范 围。就本专利所附权利要求书中所述的任何术语是以与在本专利中的单一含义 一致的方式来引用的,这样做的目的只是为清楚而不混淆读者,而不是要使这 样的权利要求术语以隐式或其它方式受限于该单一含义。最后,除非权利要求 元素是通过陈述词语“装置”和功能而没有陈述任何结构来定义的,否则任何 权利要求元素的范围不应当基于对35U.S.C§112第六节的应用来解释。

许多发明功能和许多发明原理最好用软件程序或指令和集成电路(IC)如 专用IC来实现。预期到本领域技术人员尽管有可能花费显著的努力和存在由 例如可用时间、当前技术以及经济考虑促动的许多设计选择,但在本文所公开 的概念和原理指导下将容易地有能力在最少试验的情况下生成这样的指令和 程序和IC。因此,出于简明和最小化模糊按照本发明的原理和概念的任何风险 的目的,对这样的软件和IC的进一步讨论(如果有的话)将限制于与优选实 施例的原理和概念相关的本质。

图1和2提供涉及本公开的网络和计算平台的结构基础。

图1例示网络10。网络10可以是因特网、虚拟个人网络(VPN)或允许 一或多个计算机、通信设备、数据库等相互通信地连接的任何其它网络。网络 10可经由以太网16和路由器18以及陆线20连接至个人计算机12和计算机终 端14。以太网16可以是较大因特网协议网络的子网。其它网络化资源诸如投 影机或打印机(未示出)也可通过以太网16或其它数据网络来支持。另一方 面,网络10可以经由无线通信站26和无线链接28无线地连接至膝上型计算 机22和个人数据助理24。同样,服务器30可使用通信链接32连接至网络10, 而大型机34可使用另一通信链接36连接至网络10。网络10可用于支持对等 网络通信。

图2例示计算机110形式的计算设备。计算机110的组件包括但不限于, 处理单元120、系统存储器130和将包括系统存储器在内的各种系统组件耦合 至处理单元120的系统总线121。系统总线121可以是若干类型的总线结构中 的任一种,包括存储器总线或存储器控制器、外围总线和使用各种总线体系结 构中的任一种的局部总线。作为示例,而非限制,这样的体系结构包括工业标 准体系结构(ISA)总线、微通道体系结构(MCA)总线、扩展的ISA(EISA) 总线、视频电子技术标准协会(VESA)局部总线和外围部件互连(PCI)总线 (也被称为Mezzanine总线)。

计算机110一般包括各种计算机可读介质。计算机可读介质可以是可由计 算机110访问任何可用介质,并且包括易失性与非易失介质、可移动和不可移 动介质。作为示例而非局限,计算机可读介质包括计算机存储介质和通信介质。 计算机存储介质包括以用于储存诸如计算机可读指令、数据结构、程序模块或 其它数据等信息的任一方法或技术实现的易失性和非易失性,可移动和不可移 动介质。计算机存储介质包括但不限于,RAM、ROM、EEPROM、闪存或其 它存储器技术,CD-ROM、数字多功能盘(DVD)或其它光盘存储、磁盒、磁 带、磁盘存储或其它磁存储设备,或者可用于存储所需信息并且可由计算机110 访问的任何其它介质。通信介质一般体现为已调制数据信号诸如载波或其它传 输机制中的计算机可读指令、数据结构、程序模块或其它数据,并且包括任何 信息传递介质。术语“已调制数据信号”指以对信号中的信息进行编码的方式 设置或改变其一个或多个特征的信号。作为示例而非限制,通信介质包括线接 介质诸如线接网络或直接线接连接和无线介质诸如声音、射频、红外和其它无 线介质。上述任何各项的组合也应当包括在计算机可读介质的范围内。

系统存储器130包括易失性和/或非易失性存储器形式的计算机存储介质, 诸如只读存储器(ROM)131和随机存取存储器(RAM)132。基本输入/输出 系统133(BIOS),包含例如在启动时帮助计算机110内的诸元素之间传送信 息的基本例程,通常存储在ROM 131中。RAM 132通常包含处理单元120可 以立即访问和/或目前正在操作的数据和/或程序模块。作为示例,而非限制, 图2示出了操作系统134、应用程序135、其它程序模块136和程序数据137。

计算机110也可包括其它可移动/不可移动、易失性/非易失性计算机存储 介质。仅作为示例,图2例示读写不可移动、非易失性磁介质的硬盘驱动器141、 读写可移动非易失性磁盘152的磁盘驱动器151和读写可移动非易失性光盘 156诸如CD ROM或其它光介质的光盘驱动器155。可以在示例性操作环境下 使用的其它可移动/不可移动、易失性/非易失性计算机存储介质包括,但不限 于,盒式磁带、闪存卡、数字多功能盘、数字录像带、固态RAM、固态ROM 等。硬盘驱动器141通常由诸如接口140的不可移动存储器接口连接至系统总 线121,磁盘驱动器151和光盘驱动器155通常由诸如接口150的可移动存储 器接口连接至系统总线121。

上面讨论并在图2中例示的这些驱动器及其相关联的计算机存储介质为 计算机110提供对计算机可读指令、数据结构、程序模块和其它数据的存储。 例如,在图2中,硬盘驱动器141被示为存储操作系统144、应用程序145、 其它程序模块146和程序数据147。注意,这些组件可以与操作系统134、应 用程序135、其它程序模块136和程序数据137相同或不同。操作系统144、 应用程序145、其它程序模块146和程序数据147这里给予不同的数字以说明 至少它们是不同的副本。用户可通过输入设备诸如键盘162和光标控制设备 161(通常指的是鼠标、跟踪球或触摸板)将命令和信息输入至计算机20中。 照相机163诸如网络摄像头(webcam)可捕捉和输入关联于计算机110的环境的 照片,诸如提供用户的照片。网络摄像头163可按需捕捉照片,例如在用户指 示的时候,或者可在计算机110的控制下周期性地拍照。其它输入设备(未示 出)可包括话筒、操纵杆、游戏手柄、圆盘式卫星天线、扫描仪等等。这些和 其它输入设备经常通过耦合至系统总线的输入接口160连接至处理单元120, 但可由其它接口和总线结构诸如并行端口、游戏端口或通用串行总线(USB) 来连接。监视器191或其它类型的显示设备也可通过接口诸如图形控制器190 连接至系统总线121。除监视器之外,计算机还可包括其它外围输出设备诸如 扬声器197和打印机196,它们可通过输出外围接口195来连接。

计算机110可使用至一或多个远程计算机诸如远程计算机180的逻辑连接 在网络化环境中运行。远程计算机180可以是个人计算机、服务器、路由器、 网络PC、对等设备或其它常见网络节点,且通常包括上文相对于计算机110 描述的许多或所有元件,尽管在图2中只示出存储器存储设备181。图2所示 的逻辑连接包括局域网(LAN)171和广域网(WAN)173,但也可包括其它 网络。这类联网环境在办公室、企业级计算机网络、内联网和因特网中是很常 见的。

当在LAN联网环境中使用时,计算机110通过网络接口或适配器170连 接至LAN 171。当在WAN联网环境中使用时,计算机110通常包括调制解调 器172或用于在诸如因特网等WAN 173上建立通信的其它装置。调制解调器 172,可以是内置或外置的,通过输入接口160或其它合适的机制连接至系统 总线121。在网络化环境中,相对于计算机110描述的程序模块或其部分可被 存储在远程存储器存储设备中。作为示例,而非限制,图2示出了远程应用程 序185驻留在存储器设备181上。

通信连接170172允许设备与其它设备通信。通信连接170172是通信介 质的示例。通信介质一般体现为已调制数据信号诸如载波或其它传输机制中的 计算机可读指令、数据结构、程序模块或其它数据,并且包括任何信息传递介 质。“已调制的数据信号”是其一或多个特征以将信息编码到信号中的方式设 置或改变的信号。作为示例而非限制,通信介质包括包括线接介质诸如线接网 络或直接线接连接以及无线介质诸如声音、RF、红外和其它无线介质。计算机 可读介质可包括存储介质和通信介质两者。

图3描绘示例性客户机/服务器网络200,诸如企业资源计划系统,它可与 图1的网络10相似或者耦合至图1的网络10。客户机/服务器网络200可包括 由网络210、212、214耦合的各个系统202、204、206、208。网络210、212、 214可是线接或无线的,并且可支持因特网协议版本6(IPv6)和安全通信协 议诸如加密套接字协议层(SSL)。在一个示例中,因特网可用作网络210、 212、214。系统202是服务器系统,它可包括一个服务器216或多个服务器。 服务器系统202可以是业务企业服务器系统、SQL或其它数据库管理服务器系 统或者消息收发和企业协作服务器系统,然而可以包括不同的服务器类型或服 务器利用。

系统204、206是各自包含网络通信设备218、220的客户机系统,网络通 信设备包括但不限于:个人计算机、电话、个人数字助理、机顶盒、电视和娱 乐系统等等。系统208包括有效耦合至服务器系统202并且存储数据项的数据 库222。在一个示例中,数据库222可将数据项存储在表的行中,并且数据库 222可维护多个表以存储数据。数据项可由服务器系统202管理,这些数据项 被存储在具有对应于不同数据项的一或多个行的各种表中。在一个示例中,网 络通信设备218、220可将进行读和/或写操作的不同用户与服务器216相关以 访问和/或修改存储在数据库222内的数据项。一般而言,服务器系统202使用 本文所述的并发控制技术来允许多个用户204、206同时读或更新数据库222 内的数据项、同一表内的数据项或同一数据项。在另一个示例中,使用上述系 统200,服务器216可允许多个客户机204、206参与由服务器系统202管理的 服务器应用程序。或者,客户机204、206可在本地执行这些应用程序。应用 程序可包括应用程序代码,它们包含用于提供读和/或写请求的读和/或更新语 句。如本文使用的,术语‘更新’这里定义为表示对数据项的任何修改,包括 但不限于修改数据、写新数据或删除数据。

尽管客户机系统204、206各自被示为包括一个网络通信设备218、220, 但应当理解可使用不同数量的网络通信设备。同样,服务器系统202可包括不 同数量的服务器,而数据库系统208可包括不同数量的数据库。另外,尽管服 务器216、网络通信设备218、220和数据库222各自被示为设置在其自己的系 统202、204、206、208内,但应当理解服务器216、网络通信设备218、220 和数据库222可在同一系统内设置。还应当理解,可提供多个系统,包括数百 或数千个客户机系统和数据库系统。尽管下面的公开大体描述在同一数据项上 执行并发写操作的多个数据事务,这包括在一个服务器216与多个同时进行操 作的用户或应用程序之间的交互,但应当理解,一或多个服务器可同时操作, 各自具有进行数据事务以便在一或多个数据项上执行写操作的一个或多个并 发用户或应用程序。另外,尽管下面的公开一般描述在服务器系统操作系统的 内核数据访问层内实现的并发控制技术,但应当理解,可使用并发控制技术的 各种其它实现。下面提供计算机代码的各种示例,其中一些是用X++编程语言 (它是一种简单的面向对象语言)或C++编程代码编写的,尽管可使用各种其 它编程语言,包括其它的面向对象语言。

一般而言,在涉及读操作的数据事务期间,服务器系统202接收来自正由 用户执行的应用程序诸如业务过程的读请求。使用本文描述的并发控制技术, 服务器系统202可允许并发用户对数据项进行不受限制的读操作,因为仅读数 据项是不会引起完整性损失的。因此,允许应用程序读行和相应的数据项而无 需获得读操作的排它锁,从而允许在服务器系统202内的最大并发。另外,具 有更新数据意图的读操作也可执行而无需获得排它锁,从而使读操作展现为读 未提交(uncommitted)数据。如本文进一步描述的,数据完整性可通过比较在 更新期间受影响数据的版本标识来维护。

另一方面,在涉及写操作的数据事务期间,服务器系统202可确保数据一 致以避免丢失更新。数据操作可用三个阶段处理:读阶段,确认阶段和实际进 行写操作的写阶段。在一个示例中,服务器系统202处理读阶段,而数据库222 处理确认和写阶段。每一写请求之前是读请求。通过接收初始读请求、选择数 据项和在数据正从数据库取出用于后续更新的时候将结果提供给应用程序来 处理写请求。应用程序随后修改数据项并且向服务器系统202或数据库222提 供更新。可在对应于数据项的行上启动更新锁,选择数据项,并且确认正在更 新的数据项。在确认期间,可触发一致性检验算法,它通过比较在初始读的数 据项和正在更新数据项的版本标识(在此也称为数据项的“RecVersion(记录版 本)”)来判断数据项是否在另一个数据事务期间被更新。换言之,可以判断 正在更新的数据项的版本是否与初始读的数据项的版本相同。如果这些版本相 同,则允许进行更新,将改变提交给数据库222并且一旦该数据事务提交就对 于对应于数据的行解锁。如果版本不同,则服务器系统202检测冲突并且提出 更新冲突异常,并向应用程序提供处理该冲突的机会以尝试弥补在该数据事务 内的更新冲突而无需自动回卷或异常中止数据事务。如果应用程序不能弥补更 新冲突,则服务器系统202回卷数据事务。应用程序可意识到该异常并且可回 卷应用程序代码至某一位置,在这里应用程序在以后可尝试该写操作。服务器 系统202从而在写操作期间提供并发控制而不用在从数据库取出数据项用于后 续更新时锁定对应于数据项的行。代之以在真正的更新期间使用行级锁定,从 而允许其它数据事务读数据项或更新表和/或数据库222内的任何其它数据项。 如果数据项在取出与更新之间被另一个数据事务修改,则检测到该修改,并且 生成、处理异常并且可从内核数据访问层抛出该异常至应用程序代码。

乐观与悲观并发控制管理

除了提供乐观并发控制之外,服务器系统202还可维护悲观并发控制选 择。相应地,向服务器系统202提供各种并发控制选择,包括但不限于全局启 用乐观并发控制、全局禁用乐观并发控制和启用每一个表的乐观并发控制。全 局启用乐观并发控制允许内核在乐观并发控制下对数据库222内的所有表进行 数据事务。全局禁用乐观并发控制指示内核在悲观并发控制下对数据库222内 的所有表进行数据事务。通过启用每一个表的乐观并发控制,数据库222内的 各个表被配置为在特定的并发控制方法下进行操作。例如,所有表可被初始设 置为启用乐观并发控制,并且用户可适当地在逐个表基础上改变该值。

可提供全局乐观并发控制开关以在启用和禁用全局乐观并发控制之间以 及在启用或禁用每一个表的乐观并发控制之间切换。可将全局乐观并发控制开 关设置为存储在数据库222内的设置标志,它在并发控制支持的各个选项之间 切换服务器系统202可在激活服务器系统202时检查全局乐观并发控制的状 态,并且取出全局设置并将其存储在存储器中。当激活客户机时,会话调用传 递回这些开关值至该客户机,客户机在本地设置这些值。每一个表的乐观并发 控制在运行时间被添加至表属性集合并且呈现在表的应用程序对象树属性上。 每一个表的乐观并发控制可使用元数据存储中的标志的未使用位,可用位“0” 定义默认值,在该情形中将每一个表的乐观并发控制属性设置为“真”。

在一些情形中,应用程序可能需要来自上述配置的异常。例如,乐观并发 控制可能需要对于个别应用程序在语句级上被禁止,即使对于大多数其它应用 程序,在启用乐观并发控制的情况下设置特定的表。因而,内核可在编程语言 中引入关键词诸如下述的“pessimisticlock(悲观锁)”和“optimisticlock(乐观锁)”, 以覆盖每一个表的和全局的乐观并发控制开关。下面伪计算机代码实现的例子 例示语句级的悲观并发控制管理的示例,其中乐观并发控制是全局启用的(或 者在表级启用),但一特定应用程序需要悲观锁:

       ...

       {

           CustTable custTable;

           ;

           ttsbegin;

              select pessimisticlock CustTable where CustTable.Currency==

′CAD′;

              CustTable.PriceGroup=′PUB′;

            CustTable.update();

         ttscommit;

       }

关键词“pessimisticlock”允许内核不检索版本标识“RecVersion”(它标 识正在更新的数据项的版本),从而覆盖了乐观并发控制并且按照悲观并发控 制允许在必要的更新锁就位的情况下来读数据项。

下面伪计算机代码实现的例子例示语句级的乐观并发控制管理的替换示 例,其中乐观并发控制被全局禁用(或者在表级禁用),但特定的应用程序要 求乐观锁:

       ...

       {

          CustTable custTable;

          ; 

          ttsbegin;

             select optimisticlock CustTable where CustTable.Currency==

′CAD′;

             CustTable.PriceGroup=′PUB′;

             CustTable.update();

          ttscommit;

       }

版本标识

如前所示,每一数据项与版本标识(“RecVersion”)关联。数据库222 内使用乐观并发控制的每一个表包含与版本标识有关的列。当在数据库222中 创建表时,内核数据访问层将版本标识列添加至表定义。如果在数据库222内 存在的表没有版本标识列,则可将版本标识列添加至现有表并且服务器系统 202可为表中的所有列自动生成版本标识值。当将记录插入至具有版本标识列 的表中时,服务器系统202可自动为该新记生成新的版本标识值。对于使用乐 观并发控制的所有写操作,内核数据访问层读所有正在取出的行的版本标识 值,并且存储这些版本标识值用于随后检查数据项的一致性以检测更新冲突。 当更新行内的数据项时,内核数据访问层检索最初从数据库222取出该行时该 行的版本标识值并将它添加至更新语句谓词。如果更新语句谓词没有找到匹配 的版本标识值,则检测到更新冲突并且提出更新冲突异常至尝试处理该冲突的 应用程序。如果写操作涉及删除先前读过的记录,则内核数据访问层将版本标 识值添加至语句谓词以判断正在删除的记录是否在之前已被修改。

为经更新的数据生成的新的版本标识值可在内核数据访问中维护以便跨 多个数据操作维护事务语义。结果,可在服务器系统202中而不是在数据库222 中生成新的版本标识值。

在一个示例中,版本标识可以是唯一标识数据项的服务器时戳。在另一个 示例中,版本标识可以仅仅是递增的整数。然而,在去串行化数据事务中,为 了最大化可以发生的并发数据事务的数量,可使用读未提交隔离级,它允许一 个数据事务在另一个数据事务之内和之外都可以选择和更新数据项。一般而 言,隔离级指的是事务必须与其它事务隔离的程度。然而,为了增加并发性, 使用读未提交隔离级来利用不是所有数据事务始终需要完全隔离的可能性。结 果,数据正确性在没有合适版本标识的情况下可能受到损害。采用任一上述版 本标识示例,存在由先前的数据事务所作的更新未被正确检测到的可能性,因 而导致重写,如下面的图表所示:

  时间   事务1  事务2   事务3   数据库的   表t的行r的   RecVersion   T1   Ttsbegin(开   始);   选择进行更   新;   存储器中的   RecVersion=   v;   V(已提   交)   T2   更新;   存储器中的   RecVersion=   v+1;   V+1(未提   交)   T3  Ttsbegin;  选择进行更  新;  存储器中的  RecVersion=V+1

  T4   异常中止;   V(已提   交)   T5  Ttbbegin;  选择进行更  新;  存储器中的  RecVersion=v;   V(已提   交)   T6  更新;  提交;  因为  RecVersion匹配  提交成功;   V+1(已提   交)   T7   更新;   提交;   因为   RecVersion匹配   提交成功;   这可以重写由   事务3在T6所作   的改变.   V+1(已提   交)

如上所示,第一数据事务可读初始版本标识值V并且更新数据项,从而 使得版本标识值被更新为V+1。在提交写操作之前,第一数据事务可异常中止 写操作,并且将版本标识值复位为V。然而,第二数据事务在异常中止之间开 始写操作并且读版本标识值V+1。第三数据事务在异常中止之后开始写操作, 将版本标识值V存储在存储器中并且在第二数据事务提交其写操作之前提交 写操作。因此,第三数据事务还将版本标识值更新为V+1,因为存储在存储器 中的版本标识值V匹配正在更新的数据项的版本标识值V。当第二数据事务提 交其写操作时,存储在存储器中的版本标识值V+1匹配正在更新的数据项的版 本标识值V+1。因此,第二数据事务表现为它正在基于第一数据事务的异常中 止的更新来更新数据项,而实际上重写了由第三数据事务所作的更新。

为了解决这种可能性,可提供跨所有服务器分配唯一地标识数据项的随机 数作为版本标识。在一个示例中,随机数的种子可基于数据项的内容本身,从 而确保随机数对于该数据项在时间、用户和服务器系统202上是唯一的,并且 数据项在更新之后的每一版本具有关联于其的唯一版本标识值。在另一个示例 中,随机数的种子是用于通过随机生成算法生成随机数的随机种子。

随机数的产生可使用加密应用程序编程接口CryptGenRandom。 CryptGenRandom函数用比通常的随机类更为随机的加密的随机字节填充缓冲 区。可根据一或多个下列源为CryptGenRandom函数产生不确定性的度量(即 熵):内核切换中的线程、当前进程标识符、自引导起的时钟数、当前时间、 存储器信息和对象存储器统计。CryptGenRandom函数可通过一个静态方法来 初始化一次。下面示出CryptGenRandom函数的初始化的示例。使用 CRYPT_NEWKEYSET,CRYPT_MACIIINE_KEYSET,因此用于服务的钥可以 访问。尽管使用C++型的符号来描述初始化,但初始化不限于此。

      HCRYPTPROV dbRecbuf::dbrb_hCryptProv=

dbRecbuf::InitHCryptProv();

      HCRYPTPROV dbRecbuf::InitHCryptProv()

      {

if(!CryptAcquireContext(&(dbrb_hCryptProv),NULL,NULL,PROV_RSA_FULL,

CRYPT_MACHINE_KEYSET))

        {

           DWORD dwError=GetLastError();

           AXTRACE(″hCryptProv first try acquire context failed with%d″,

dwError);

           //如果(NTE_BAD_KEY==dwError)

           {

    if(!CryptAcquireContext(&(dbrb_hCryptProv),NULL,NULL,PROV_RSA_

FULL,CRYPT_NEWKEYSET|CRYPT_MACHINE_KEYSET))

               {

                  dwError=GetLastError();

                  AXTRACE(″hCryptProv second try acquire context

failed with%d″,dwError);

                  _ASSERT(FALSE);

               }

            }

       else

           {

              _ASSERT(FALSE);

           }

       }

       _ASSERT(NULL!=dbrb_hCryptProv);

       AXTRACE(″hCryptProv initialized to%d″,dbrb_hCryptProv);

       return dbrb_hCryptProv;

       }

此外,可添加使用CryptGenRandom来生成下一个版本标识RecVersion 的方法来确保该函数生成正的版本标识。可为其中已经提交了数据事务的数据 项的每一成功更新生成新的版本标识值。下面示出该函数的示例:

        int dbRecbuf::SetNextRecVersion()

        {

          int iReturnCode=CQL_OK;

          boolean fRetry=true;

          while(fRetry)

          {

             fRetry=false;

             if(CryptGenRandom(dbrb_hCryptProv,sizeof(this->dbrb_iRecVersion

),(byte*)(&this->dbrb_iRecVersion)))

             {

               if(this->dbrb_iRecVersion<0)

               {

                 this->dbrb_iRecVersion=-this->dbrb_iRecVersion;

               }

               else if(0==this->dbrb_iRecVersion)

               {

                 fRetry=true;

               }

             }

             else

             {

                 iReturnCode=CQL_EXCEPTION;

             }

           }

           return iReturnCode;

        }

禁用悲观锁

如前所述,可在悲观并发控制与乐观并发控制之间提供选择,例如通过全 局乐观并发控制开关。然而,当更新数据项时,应用程序的应用程序代码可在 更新语句内包含自动默认启用悲观锁的触发器。例如,在X++编程代码中,悲 观锁可通过SELECT和WHILE SELECT语句内的forupdate(进行更新)提示来 触发,并且使用forupdate提示的应用程序可基于服务器系统202平台支持悲 观锁的假设工作。

为了禁用悲观锁但维护与采用悲观锁的应用程序的后向兼容性,乐观并发 控制可移除、忽略或重新解释更新语句中的触发器以在乐观并发控制期间禁用 悲观锁。例如,触发器可在每一语句、每一个表和全局乐观并发控制开关的上 下文中重新解释。更具体地,触发器不会引起更新锁被保持在数据库中。相反, 触发器可指定应用程序的意图。例如,触发器可指定意图是否仅是读操作还是 意图是否是要使用读操作来进行将来的更新。基于这些开关,内核数据访问层 可决定更新锁是否应当保持(悲观)或者行的版本标识是否应当取出(乐观)。 在另一个示例中,在X++编程内,将用于获得数据库更新锁的数据库提示从 SELECT和WHILE SELECT语句中移除而不改变用于悲观锁的应用程序代码。 下面通过从SELECT和WHILE SELECT语句中移除数据库提示来示出禁用悲 观锁的示例:

        SomeFunction

        {

            ...

           ttsbegin;

           While select table 1 forupdate where table 1.somePredicate=someValue

           {

              ... 

              If(CheckCondition(table 1)

              {

                   ComputeNewValue(table 1);

                   table 1.update();

               }

            }

            ttscommit;

        }

图4是乐观并发控制例程300的示例,它可由服务器系统202执行,具体 是由服务器系统202内核的内核数据访问层执行,以在更新表内的数据项的写 操作期间禁用悲观锁,其中已经为表启用了乐观并发控制(无论是全局还是在 每一个表的基础上)。如前所述,在每一写请求之前是读操作。只要在乐观并 发控制模式中读数据项,内核数据访问层就读它取出的所有对应行的版本标识 值并且存储它们供将来使用,诸如对所取出行的数据项执行更新的时候。在读 请求之后,保持无锁直至调用更新数据项的写请求为止。在本文描述的乐观并 发控制技术中,当更新数据项(即表内的行)时,内核数据访问层检索在读操 作期间取出的对应行的版本标识值,并且将该版本标识值添加至更新语句谓词 以便查看该数据项是否已经被修改了。如果更新涉及删除先前读的数据项,则 内核数据访问层添加版本标识值至更新语句谓词以查看该数据项是否已经被 修改了。内核数据访问层请求数据库222进行检查以查看是否有任何其它数据 事务在正在更新的数据项被读之后改变了它。如果检测到更新冲突,则抛出 UpdateConflict(更新冲突)异常。如下面将进一步描述的,代替在抛出 UpdateConflict异常时立即异常中止数据事务,异常可在数据事务内处理。

参考图4,开始于框302,乐观并发控制例程300在数据事务期间接收来 自应用程序的读请求。如果读请求与带有更新行意图的读操作诸如 “forupdate”、“pessimisticlock”或“optimistic lock”无关,如在框304确定 的,则在框306收到的任何后续的更新请求在框308不会被允许并且可终止事 务。如果读请求与带有更新行意图的读操作有关,则例程300可判断乐观并发 控制是否应当应用,诸如通过在框310判断对于该表是否已经启用乐观并发控 制。例程300还在框310判断版本标识是否将被检索并用于更新。

为了判断是否需要乐观并发控制,如在框310判断的,可向内核数据访问 层提供集中式储存库用于查找有关乐观并发控制是否应当应用以及版本标识 是否应当检索的计算。这样的计算对一个数据对象诸如数据项、行、表等只执 行一次。在一个示例中,这样的判断可由检查以查看行的版本标识是否需要为 更新和删除而使用RecVersion进行检查的方案来实现。在一或多个下列情形中 可能不需要或者不使用版本标识检查:在未读RecVersion时、对于行集合操作、 如果表或行明确地标记为要求下面进一步描述的列比较、或者如果更新是也在 下面进一步描述的相对更新。下面是这样一个方案 SqlStmt::IsRecVersionCheckNeededForupdate的示例,它检查以查看RecVersion 检查是否要用于更新并且这还要求对于该表启用乐观并发控制启用。如果要使 用RecVersion检查则返回“TRUE(真)”值。

        boolean SqlStmt::IsRecVersionCheckNeededForupdate()

        {

            //_ASSERT(type_update==this->get_stmt_type()‖type_delete==

this->get_stmt_type());

            if(!m_fRecVersionCheckNeededForupdateCalculated)

            {

                  m_fRecVersionCheckNeededForupdate=

cqlParentCursor()->IsRecVersionNeededForSelect();

              m_fRecVersionCheckNeededForupdate=

m_fRecVersionCheckNeededForupdate&&!(this->update_rowset()‖

this->sqldelete());

                   m_fRecVersionCheckNeededForupdate=

m_fRecVersionCheckNeededForupdate&&!

cqlParentCursor()->IsRereadNeeded();

                 if(m_fRecVersionCheckNeededForupdate&&this->updateList())

                 {

                      m_fRecVersionCheckNeededForupdate=!

this->GetIsRelativeUpdate();

                 }

                 m_fRecVersionCheckNeededForupdateCalculated=TRUE;

            }

            return m_fRecVersionCheckNeededForupdate;

        }

或者,或结合SqlStmt::IsRecVersionCheckNeededForupdate方案,在框312 的判断可进一步由检查以查看是否要使用任何形式的更新冲突检测的方案来 实现。例如,如果需要版本标识用于更新或者如果指定用于更新的重读则使用 更新冲突检测。重读标志表示数据最初被取出到表单上并且无论其它设置是什 么都使用乐观并发控制。如果乐观并发控制被禁用,则更新冲突检测仅可用于 表单。表单,也称为报告,使用查询来取数据,其中数据源被附连至查询对象 并且更新属性确定查询是否被允许更新数据库中的数据项。另一方面,如果启 用乐观并发控制,则更新冲突检测可使用版本标识或列比较。下面是这样一个 方案SqlStmt::IsUpdateConflictDetectionNeededForupdate的示例,它检查以查看 对更新是否要使用任何形式的更新冲突检测诸如RecVersion检查或列比较。如 果要使用任何形式的更新冲突检测,则返回“TRUE”值。

       boolean SqlStmt::IsUpdateConflictDetectionNeededForupdate()

       {

            boolean fUpdateConflictDetectionNeededForupdate=FALSE;

            if(g_fDisableOcc‖

getTable(cqlParentCursor()->dataset())->IsOccDisabled())

            {

                fUpdateConflictDetectionNeededForupdate=

cqlParentCursor()->rereadOnUpdate();

            }

            else

            {

                {

                    fUpdateConflictDetectionNeededForupdate=

this->IsRecVersionCheckNeededForupdate()

                         ‖cqlParentCursor()->IsRereadNeeded();

                 }

                 else

                 {

                     fUpdateConflictDetectionNeededForupdate=

this->cqlParentCursor()->rereadOnUpdate();

                 }

            }

            return fUpdateConflictDetectionNeededForupdate;

        }

再次参考图4的框310,如果在更新期间不要使用乐观并发控制,这是使 用上面提供的示例方案确定的,则不检索版本标识并且针对在框312更新的行 在数据库中保持更新锁。在框314,可接收更新请求,并且在框316,执行对 数据项的真正的写和/或更新,这可包括将新数据写至行,删除数据项等。在框 318提交数据事务并且在框320释放更新锁。如果为该表开启乐观并发控制并 且在更新期间要使用它,如在框310确定的,则在框322从数据库检索版本标 识,在框324接收更新请求并且使用版本标识用于利用检测更新冲突例程326 的更新冲突检测。注意,在框322针对数据库不实现和保持更新锁。

检测更新冲突

图5是检测更新冲突例程326的示例,它可由服务器系统202执行,具体 地是由服务器系统202的内核数据访问层执行,以检测在更新数据项时是否已 经发生了任何冲突。换言之,检测更新冲突例程326可判断在数据项的初始取 出与更新之间该数据项是否被其它数据事务改变。更新检测冲突例程326可检 测任何这样的改变并且从内核数据访问层抛出异常给应用程序代码。

开始于框402,检测更新冲突例程326在开始时检查以查看是否存在执行 版本标识检查(RecVersion检查)或列比较的需求。例如,如果应用程序仅仅 是修改现有数据项则可使用列比较。列比较可用于后向兼容性或者按照应用程 序逻辑的指示来使用。另一方面,版本标识检查可用于所有形式的更新,诸如 修改数据或者删除数据项。下面是一个方案SqlStmt::AddOccExpressionNodes 的示例,它首先检查以查看是否需要版本标识检查或者列比较(例如,如果在 读时行被锁,则两者都不需要)且随后如果需要的话则切换至使用版本标识或 者列比较。如果要使用任何形式的更新冲突检测,则返回所得到的表达式节点, 用于执行列比较的BuildUpdateConflictDetectionExpressionNode(建立更新冲突 检测表达式节点)和用于执行版本标识检查的 BuildRecVersonExpressionNode(建立记录版本表达式节点)。

        exprNode*SqlStmt::AddOccExpressionNodes(exprNode*

originalExpressionNode)//I原始表达式代码。

        {

            _ASSERT(this->get_stmt_type()==type_update‖

this->get_stmt_ype()==type_delete);

            if(this->IsUpdateConflictDetectionNeededForupdate())

            {

                  exprNode*pxprndNewWhere=NULL;

                  //内联更新冲突应仅用于更新。

if(this->cqlParentCursor()->GetDetectUpdateConflictInStatement())

                 {

                     if(this->get_stmt_type()==type_update)

                     {

                          pxprndNewWhere=

BuildUpdateConflictDetectionExpressionNode();

                          _ASSERT(NULL!=pxprndNewWhere);

                    }

               }

               else

               {

                    pxprndNewWhere=BuildRecVersonExpressionNode();

                    _ASSERT(NULL!=pxprndNewWhere);

               }

               originalExpressionNode=

AndExpressionNodes(originalExpressionNode,pxprndNewWhere);

               _ASSERT(NULL!=originalExpressionNode);

          }

          return originalExpressionNode;

     }

回来参考图5,如果要执行列比较,如在框402确定的,则例程326可进 行至框404比较列。具体地,服务器系统202可请求数据库执行比较。在一个 示例中,使用列比较的更新语句像下面这样:

更新表1令字段1=新字段1值,其中RecID=我的RecID且字段1=字 段1旧值

下面是一个大纲SqlStmt::BuildUpdateConflictDetectionExpressionNode的 示例,它建立并返回用于使用列比较检测更新冲突的表达式节点。具体地,注 意在该更新冲突检测的特定示例中不使用版本标识。

       exprNode*SqlStmt::BuildUpdateConflictDetectionExpressionNode()

       {

          _ASSERT(NULL!=this->updateList());

          _ASSERT(NULL!=ip());

          exprNode*pxprndNewWhere=NULL;

          exprNode*pxprndWhere=NULL;

          CQL_EVAL_ELEM*pcqlvlOriginalValue=NULL;

          valueNode*pvlndOriginalValueNode=NULL;

          for(baseList<assignmentNode*>::iterator

assgnmntndAssignmentNode=updateList()->begin();

               assgnmntndAssignmentNode!=updateList()->end();

               assgnmntndAssignmentNode++)

          {

          if(DBFIELD_RECVERSION==

DBFHDL_EXT2DBFHDL((*assgnmntndAssignmentNode)->GetLeftNode()->get

Field(ip())))

           {

              continue;

           }

           pcqlvlOriginalValue=new CQL_EVAL_ELEM(

(*assgnmntndAssignmentNode)->GetLeftNode()->getField(ip()),

                   cqlParentCursor()->orig_buffer());

               pvlndOriginalValueNode=new

valueNode(pcqlvlOriginalValue);

_ASSERT((*assgnmntndAssignmentNode)->GetLeftNode()->isField());

              fieldExpr*pfldxprField=new

fieldExpr((*assgnmntndAssignmentNode)->GetLeftNode()->getField(ip()),

                     cqlParentCursor());

                pxprndNewWhere=new

eqNode(pfldxprField,pvlndOriginalValueNode);

                pxprndWhere=

AndExpressionNodes(pxprndWhere,pxprndNewWhere);

          }

          _ASSERT(NULL!=pxprndWhere);

          return pxprndWhere;

      }

如果例程326确定要执行版本标识检查,则在框406将版本标识添加至更 新语句谓词,以判断数据项是否在取出与更新之间被修改。在框408,检索取 出数据项的版本标识并与要更新的数据项的版本标识相比较。注意,如果要由 数据库222执行版本标识比较,则服务器系统202不需要代表数据库222取出 版本标识。因为版本标识对于数据项的版本是唯一的,因此取出数据项的版本 标识与正在更新的数据项的版本标识之间的任何差异(如在框410检测的)引 起在框412抛出异常。在一个示例中,可从内核数据访问层抛出异常给生成更 新语句的应用程序代码。在框412,可如下进一步所述地执行结构化异常例程, 并且在框414可由例程处理更新冲突异常。

另一方面,如果在版本标识中没有差异,如在框410确定的,则在框416 执行数据项的真正更新,这可包括将新数据写至数据项、删除数据项等等。在 框416更新数据之后,保持写锁(也称为排它锁)。在框418,提交数据事务 并且在框420释放写锁。

结构化异常

图6是结构化异常例程500的示例,每当抛出更新冲突异常时就可在框 322执行该例程。具体地,结构化异常例程500演示结构异常处理的运行时支 持的示例。如前所述,在运行时,由内核数据访问层检测更新冲突错误并且抛 出更新冲突异常。结构化异常处理构造可捕捉更新冲突,其中仅在更新冲突发 生在该构造中指定的表上时才执行捕捉锁。通过比较,在非结构化异常处理构 造中,每当在尝试块内存在更新冲突异常时就执行捕捉块。

开始于框502,数据访问内核可跟踪其中发生更新冲突异常的表实例。内 核可保存其中发生更新冲突的表实例的内核表示。在C++编程语言中,该表示 可被称为cqlCursor。在X++编程语言中,该表示可以是表示表的任何变量。具 体地,内核可将内核表示cqlCursor放在表属性中,该属性可被称为 LastUpdateConflictingTable(最后更新冲突表)属性,因此内核知道哪个表招致了 更新冲突异常。在一个示例中,该函数可由下面的方案 cqlDatasourceSql::RaiseUpdateConflitError来执行,其示例在下面提供,它提出 指示更新冲突的特定错误并且返回指示指定更新冲突异常的错误代码的整数。

      int cqlDatasourceSql::RaiseUpdateConflictError(

           int iDBError,//IDB错误代码

           interpret*pintprtInterpret)//I指向解释器的指针.

      {

            _ASSERTE(NULL!=pintprtInterpret);

       #if defined(DEBUG)

           //记录它.

eventlog->logEvent(EVLOG_WARNING,EVMSG_MSG,_T(″Update/Delete

conflict detected!″),_T(″OCC″));

         #endif

             if(x_uiUpdateConflictException!=

pintprtInterpret->isp.exceptionval)

             {

                  pintprtInterpret->isp.exceptionval=

x_uiUpdateConflictException;

                pintprtInterpret->SetLastUpdateConflictingTable(cursor());

          }

          //_ASSERTE(x_uiUpdateConflictException==

pintprtInterpret->isp.exceptionval);

             return raiseErr(iDBError,

cursor()->exceptionFailureReason(DB_OPR_RECORD_CHANGED),

pintprtInterpret);

             //int iReturncode=raiseErr(iDBError,

cursor()->exceptionFailureReason(DB_OPR_RECORD_CHANGED),

pintprtInterpret);

             int iReturncode=::raiseErr(this->dataset(),iDBError,

cursor()->exceptionFailureReason(DB_OPR_RECORD_CHANGED),

pintprtInterpret,x_uiUpdateConflictException);

             //pintprtInterpret->isp.exceptionval=x_uiUpdateConflictException;

             return iReturncode;

         }

在框504,例程500向用户或客户机通知更新冲突异常。具体地,每当跨 服务器/客户机边界进行调用时,例程500就跨服务器/客户机调用设置表属性 LastUpdateConflictingTable以便在客户机侧正确地设置该表属性。客户机维护 本地表属性。因此,任何已经招致UpdateConflict(更新冲突)异常的表应当具有 客户机侧上对该表的本地引用。因而,每当服务器系统202将要返回调用至客 户机时,它检查以查看是否存在UpdateConflict异常,并且将该引用发回至客 户机。客户机看到存在UpdateConflict异常,读该引用,在本地查找该引用并 且解释该引用。具体地,客户机可检查异常类型和本地引用,解除引用它并且 在表属性中设置该引用。

在框506,结构化异常例程500展示已经招致UpdateConflict异常的表实 例。例如,可在应用类上向应用代码展示LastUpdateConflictingTable属性。在 框508,一运行时函数启用结构化异常处理。例如,可使用字节代码作为对函 数指针表的索引。可将该函数添加至解释类并且映射至该字节代码以处理更新 冲突的结构化异常。在运行时,调用该函数并且检查异常类型和其上发生更新 冲突的表。该函数随后仅当它们都匹配时设置下一指令至捕捉块。控制随后可 传递至处理更新冲突例程414。

处理更新冲突异常

图7是图5与6示意地示出的处理更新冲突例程414的示例,并且可执行 该例程来处理在检测更新冲突例程322期间发生的任何UpdateConflict异常。 具体地且如上所述,UpdateConflict异常的处理在异常发生时可在数据事务内 处理,而不是自动异常中止数据事务。处理更新冲突例程600允许应用程序代 码重读数据,重新应用更新逻辑和再次试图更新。尽管下面描述的处理更新冲 突例程414是弥补UpdateConflict异常的示例性描述,但应当理解可使用不同 形式的弥补逻辑。

一般而言,更新语句在“尝试”块内维护,因此在尝试块内发生的任何异 常在“捕捉”块内捕捉。在一些情形中,尝试块嵌套在其它尝试块,从而创建 多个尝试块层级。处理更新冲突例程414允许UpdateConflict异常在数据事务 内处理,并且在将处理执行移回下一尝试块层级和在另一个捕捉块内重新捕捉 UpdateConflict异常之前通过试图在每一尝试块层级的捕捉块内重读和重试数 据事务来延迟异常中止数据事务。每当捕捉和处理冲突异常时,应用程序代码 可执行该操作。更具体地,应用程序可确信数据库内的数据与存储器内的对象 状态处于一致状态。一旦到达最外面的尝试块层级并且已经执行相应的捕捉块 或者事务层级已经到达“0”,就只能异常中止数据事务。

为了实现例程414,可在数据事务进入与离开每一尝试块时通过递增和递 减尝试层级计数tryLevelCount来跟踪尝试块层级。尝试层级计数可由服务器 系统202与客户机共享。用于在数据事务进入和离开尝试块时跟踪尝试块层级 的示例伪计算机代码实现可以如下:

      S_Word interpret::xal_try_sym()

      {

          ... ... ... 

          this->EnterTryBlock();

          ... ... ...

          rc=evalLoop();

          this->LeaveTryBlock();

          ... ... ...

          return rc;

      }

除了嵌套的尝试块层级之外,在一些情形中,应用程序代码可被包在其它 应用程序代码内。因此,数据事务可嵌套在其它数据事务中。为了适度地对处 理更新冲突例程414的弥补逻辑进行处理,可简单地抛出UpdateConflict异常 给最外面的数据事务,因为整个数据事务已经被卷回。用于允许最外面的事务 处理UpdateConflict异常的示例伪计算机代码实现可以如下:

      ...

      ...

          try

          {

              ttsbegin;

                this.updateNow()

              ttscommit;

           }

           catch(Exception::Deadlock)

      {

           retry;

           }

           catch(Exception::UpdateConflict)

           {

                 //可以是这里需要尝试的业务逻辑

                 if(appl.ttslevel()==0)

                     retry;

                 else

                      throw Exception::UpdateConflict;

           }

      ..

如在上例中可以看到的,支持嵌套的尝试块。代替在更新冲突发生时异常 中止数据事务,嵌套的ttsbegin(tts 开始)和ttscommit(tts 提交)块可使得嵌套层 级增加而不开始或提交新的数据事务。相反,作为外面的事务一部分来包括它。 由最外面的嵌套层级开始和提交事务,但可以在嵌套中的任何地方异常中止事 务。如果在尝试块内提出更新冲突,则事务的嵌套层级回到代码进入该尝试块 的层级。如果该层级为0则异常中止事务。可提出更新冲突,这可使用结构化 异常处理构造来捕捉,其中仅当冲突在指定的表上发生时才执行捕捉块,或者 它可使用非结构化的异常处理构造来捕捉,其中每当在尝试块内发生冲突时就 执行捕捉块。应用程序代码可用于找出哪个表招致冲突的机制。

再次参考图7,开始于框602,一旦已经抛出UpdateConflict异常,数据 事务就进入捕捉块。在框604,处理更新冲突例程414通过检查尝试层级计数 器来判断数据事务是否处于一个尝试块内。例如,如果尝试层级计数具有零值, 则数据事务不再是在尝试块内并且可在框606异常中止该数据事务。如果数据 事务在尝试块内,则应用程序代码可以试图重读和重试该数据事务。具体地, 捕捉块可在将异常抛回至下一尝试块层级之前试图预定次数的重读和重试。因 而,在框608,处理更新冲突例程414判断重试的次数是否已经超过预定的限 制。如果是,则例程414在框610将异常抛回至下一尝试块层级。

另一方面,如果重试的次数尚未超过预定的程度,则例程414在框612 在捕捉块内进行重读行和重试数据事务而不回卷或者立即异常中止数据事务。 如果成功处理了UpdateConflict异常,如在框614确定的,则可在框616清除 有关该UpdateConflict异常的相应信息并且可以提交该数据事务。例如可通过 从数据库重读数据和重试更新来成功处理UpdateConflict异常。在这样的情形 中,在数据库中不保持更新锁,但应用程序可以选择在处理代码时切换至悲观 锁,在该情形中,在读时保持更新锁并在更新之后将更新锁更新为排它锁。如 果没有成功处理UpdateConflict异常,则可将重试计数增一并且控制可传递回 框608。

用于处理UpdateConflict异常的示例伪计算机代码实现可以如下。在下面 的示例中,尝试捕捉层级是一并且允许不超过五次的重试。

     static void OCC2_SimpleCompensationLogic(Args_args)

     {

        CustTable custTable;

        int retryCount;

        ;

        ttsbegin;

        select forupdate custTable where custTable.AccountNum==′S135′

&&custTable.CustGroup==′00′;

        retryCount=0;

        try

            {

                 custTable.CreditRating=

strfmt(″%1″,str2int(custTable.CreditRating)+1);

                 custTable.update();//将在该方法内抛出UpdateConflict。

见下面

                 info(strfmt(″CreditRating=%1″,custTable.CreditRating));

            }

         catch(Exception::UpdateConflict)

            {

               if(retryCount<4)

               {

                   retryCount++;

                      custTable.reread();

                      retry;

                  }else

                  {

                      throw Exception::UpdateConflictNotRecovered;

                   }

               }

          ttscommit;

      }

用于处理具有多个更新的UpdateConflict异常的示例伪计算机代码可以如 下。如该伪计算机代码所示,由第一应用程序遇到的更新冲突导致数据事务按 照应用程序代码的指示而被异常中止。另一方面,由第二应用程序遇到的更新 冲突导致重试。

       static void Occ2Test_MultipleUpdateConflictMgmt(Args_args)

       {

           CustTable cust1;

           CustTable cust2;

           ;

           ttsbegin;

           try

           {

                select forupdate cust1 where cust1.AccountNum==′S135′&&

cust1.CustGroup==′00′;

                select forupdate cust2 where cust2.AccountNum==′S136′&&

cust1.CustGroup==′00′;

                cust1.CreditRating=strfmt(″%1″,str2int(cust1.CreditRating)+1);

                cust2.CreditRating=strfmt(″%1″,str2int(cust2.CreditRating)+1);

                cust2.update();

                cust1.update();

            }

            catch(Exception::UpdateConflict,cust1)

        {

                ttsabort;

                throw Exception::UpdateConflictNotRecovered;

            }

            catch(Exception::UpdateConflict,cust2)

            {

                cust2.reread();

                cust2.CreditRating=

strfmt(″%1″,str2int(cust2.CreditRating)+1);

                cust2.update();

             }

             ttscommit;

       }

上面提供的用于处理具有多个更新的UpdateConflict异常的伪计算机代码 示例使用结构化异常处理机制。或者,下面提供用于对具有多个更新的 UpdateConflict异常进行非结构化处理的示例伪计算机代码。再次,第一应用 异常中止数据事务,然而第二应用重试数据事务。

      ..

          catch(Exception::UpdateConflict)

          {

               if(appl.LastUpdateConflictingTable()==cust1)

               {

                 ttsabort;

                 throw Exception::UpdateConflictNotRecovered;

               }

               else if(appl.LastUpdateConflictingTable()==cust2)

                        {

                          cust2.reread();

                          cust2.CreditRating=

strfmt(″%1″,str2int(cust2.CreditRating)+1);

                          cust2.update();

                         }

                 }

          ..

相对更新

如前所述,在一些情形中,更新可能与相对更新有关,在该情形中不使用 版本标识检查。无论如何,可使用相对更新以便减少更新冲突,并且这对于实 数和整数字段类型特别有用。如果对数据项的更新是相对的,与绝对相反,则 可以下面的形式执行更新:更新表1令字段1=字段1+变化,而绝对更新如 下执行:更新表1令字段1=最终值。例如,两个同时发生的数据事务各自想 要将一个字段值递减“二”,其中该字段具有初始值“八”,这与为该字段指 定一个新值相反。相对更新递减使得响应于第一数据事务,初始值被递减二, 而响应于第二数据事务新值被再次递减二,以提供最终值“四”。相对更新的 优点是相对更新不重写另一用户的改变,即使改变在读与更新之间发生,因为 相对更新格式的本质使其能够抵抗另一用户的改变。因此,如果所有字段使用 相对更新来更新,则可避免版本标识检查。为了实现相对更新,更新字段可被 标记为使用相对更新。

图8是相对更新例程700的示例,它可用于在数据项上执行相对更新。开 始于框702,相对更新例程700判断更新语句是否调用相对更新和/或正在更新 的字段是否被标记为使用相对更新。如果更新不是相对更新,则控制可引用回 至乐观并发控制例程300。下面提供用于判断相对更新是否可使用的伪计算机 代码示例。如提到的,因为不使用版本标识,所以忽略关联于版本标识的字段。

     void SqlStmt::SetIsRelativeUpdate(boolean fIsRelativeUpdate)

     {

          m_fIsRelativeUpdate=fIsRelativeUpdate;

cqlParentCursor()->SetIsRecVersionUpdateRelative(fIsRelativeUpdate);

          m_fIsRelativeUpdateCalculated=TRUE;

     }

     boolean SqlStmt::GetIsRelativeUpdate()

     {

          if(!m_fIsRelativeUpdateCalculated)

          {

                m_fIsRelativeUpdate=TRUE;

                for(baseList<assignmentNode*>::iterator

assgnmntndAssignmentNode=updateList()->begin();

                      assgnmntndAssignmentNode!=updateList()->end();

                      assgnmntndAssignmentNode++)

                {

_ASSERT((*assgnmntndAssignmentNode)->GetLeftNode()->isField());

                      DBFHDL_EXT fxtFieldHandle=

(*assgnmntndAssignmentNode)->getField(ip());

                      if(!isSystemField(fxtFieldHandle))

                      {

                            hdlField*phdlfldField=

getField(cqlParentCursor()->dataset(),

DBFHDL_EXT2DBFHDL(fxtFieldHandle));

                            _ASSERT(phdlfldField);

                            if(!(phdlfldField->info()->dbfflags&

DBF_RELATIVE))

                            {

                                m_fIsRelativeUpdate=FALSE;

                                break;

                             }

                        }

                  }

                  m_fIsRelativeUpdateCalculated=TRUE;

             }

             return m_fIsRelativeUpdate;

        }

应当维护事务语义,其中可以保持对表中同一行的潜在多个引用并且可以 在这多个引用上执行多个操作。当执行更新时,保持对正在更新的相同行的引 用的变量上的版本标识好像在同一事务中读它们一样地被更新。除了 RecVersion列之外,可添加另外两个列:TransactionRecVersion(事务记录版本) 和OriginalRecVersion(原记录版本),其中为每一新的事务生成唯一的 TransactionRecVersion,并且当在该事务内的第一更新触及行时,使用刚产生 的新RecVersion来更新TransactionRecVersion,并且将旧的RecVersion赋给 OrignalRecVersion。如果TransactionRecVersion匹配当前的事务的RecVersion 并且OrignalRecVersion匹配存储器中的RecVersion(这意味着该事务拥有该 行)或者如果RecVersion匹配存储器中的RecVersion,则允许更新通过。每当 进行更新时,可更新RecVersion。

与任何更新一样,例程700可更新所更新字段的值标识。然而,因为相对 更新例程700不检查值标识,则如果值标识是使用上述技术用新值来更新的话, 存在更新会重写另一个数据事务更新的可能,如由下面的图表所示:

  时间   事务1   事务2   数据库中表t的行r   的RecVersion.   T1   Ttsbegin;   选择进行更   新;   存储器中的   RecVersion=v;   Ttsbegin;   选择进行更新r1;   选择进行更新r2;   R1.RecVersion=v;   R2.RecVersion=v;   V(已提交)   T2   更新;   存储器中的   RecVersion=v1;   提交;   V1(已提交)   T3   以相对模式更新r1;   因为RecVersion没被检   查因此更新成功;   R1.RecVersion=v2;   R2.RecVersion=v2;(更   新此使得可以更新通过r2)   V2(已提交)   T4   以绝对模式更新;   提交;   V3(已提交)

  因为版本匹配提交成功;   这可以重写由事务1在   T2作出的改变.

如上所示,第一数据事务可读初始版本标识值V并且更新数据项从而使 得版本标识值被更新为V1。然而,第二数据事务在更新之前开始写操作,并 且执行两个更新,其中第一更新是相对更新而随后的第二更新是绝对更新。第 二数据事务的第一更新不检查版本标识值V1,因为该更新是相对更新。无论 如何,第一更新提供新版本标识值V2。第二数据事务的第二更新在读期间取 得版本标识值V2,在更新时使用该版本标识值V2,更新数据项并且成功地提 交该数据事务,因为在更新期间的版本标识值V2匹配在第二更新之前最初读 的版本标识值V2。因此,第二数据事务可重写由第一数据事务所作的改变。

为了解决这种可能性,当作为相对更新执行更新时,新版本标识被计算为 相对版本标识。具体地,例程700计算版本标识的新值,如在上面框704所提 供的。在框706,相对更新例程700计算新版本标识与旧版本标识之间的差, 并且发出更新以将所更新数据项的版本标识设置为版本标识值加上该差,这可 被表示为框708的“更新…令RecVersion=RecVersion+增量”。在框710 执行更新。因而,对于在同一事务中所读的同一行的所有引用,也可使用该差 来更新版本标识。如果没有其它事务更新该行,则数据库中的版本标识值匹配 所有在同一事务中用相同原版本标识值读取的存储器中引用的版本标识值,并 且将来的更新在这些行上继续。另一方面,如果在进行相对更新之前由某个其 它事务更新了该行,则版本标识不匹配,并且任何将来的更新将引起 UpdateConflict异常。

表间依赖

在一些情形中,表内一些列的值(例如表A)可基于另一个表(例如表B) 的一些列的值来计算。例如,对表A内的一个数据项的更新可在开始时从表B 读一个值,但在更新表A内的该数据项之前,另一个用户更新表B的值。结 果,对表A内的数据项的后续更新基于来自表B的过时值。

为了解决由表间依赖引起的这类一致性值,内核数据访问层可为应用程序 代码提供可重复读提示。可重复读提示转换成服务器系统202的可重复读锁提 示RepeatableRead,后者对取出数据保持共享锁直至数据事务结束为止。可重 复读锁提示仅应用于特定的读语句并且不应用于整个数据事务。在没有可重复 读锁提示的时候,在读操作之后立即释放共享锁。这可防止其它用户在提交数 据事务之前更新该行。共享锁相互兼容,因此在相同脚本上运行的多个用户不 会相互阻塞。

图9是表间依赖例程800的示例,它可用于根据来自另一个表的值来更新 数据项。在框802,例程800可接收来自应用程序代码的可重复读提示,它转 换成服务器系统202的可重复读锁提示。在没有可重复读提示的时候,在框804 的读操作之后立即释放共享锁并且提交事务。如果提供可重复读提示,则在框 806,在数据事务期间在表A和表B上提供针对取出数据的共享锁以防止其它 用户在事务提交之前更新该数据项。在框808从表B取出数据项。在框810, 基于来自表B的经更新数据项计算用于表A的数据项并且在框812进行更新。 在框814,释放共享锁并且提交数据事务。

尽管上述文本阐述本发明的众多不同实施例的详细描述,但应当理解,本 发明的范围是由本发明所附的权利要求书的语言来定义的。详细描述应当解释 为仅是示例性的并且不描述本发明的每一可能的实施例,因为描述每一可能的 实施例即使不是不可能也是不实际的。可使用当前技术或者使用在本专利提交 日之后开发的技术来实现众多替换实施例,它们仍将落入定义实施例的权利要 求书的范围。

因而,可在本文描述和例示的技术与结构中作出许多修改和变化而不 脱离本发明的精神与范围。因此,应当理解,本文描述的方法和设备仅是 示例性的并且不限制本发明的范围。

去获取专利,查看全文>

相似文献

  • 专利
  • 中文文献
  • 外文文献
获取专利

客服邮箱:kefu@zhangqiaokeyan.com

京公网安备:11010802029741号 ICP备案号:京ICP备15016152号-6 六维联合信息科技 (北京) 有限公司©版权所有
  • 客服微信

  • 服务号