服务器 频道

DB2通用数据库的并发性

  【IT168 服务器学院】 在数据库管理系统(DBMS)的领域中,术语“并发性”用于表示不止一个应用程序基本上(从用户的角度来看)同时访问同一数据的能力。因为 DBMS 的主要优点之一就是可以在多个用户和多个应用程序中共享数据,所以数据库系统应该提供一种管理并发访问数据的方法。DBMS 必须确保维护数据的一致状态和数据的完整性。

  取得该效果的一种方法就是实施只串行(serial-only)模式来处理数据库请求。即每个事务都要等待另一事务(具有更高的优先权或者比它早启动)完成其工作。然而,对于现在的在线系统和客户异常来说,这种处理方式所产生的性能水平简直令人无法接受。

  而另一种方法就是,DBMS 可以通过 的方式管理多个应用程序对数据的访问。锁是一种软件机制,用于在维护数据完整性和一致性的同时,允许尽可能大的吞吐量(通过最大限度地并发访问数据)。

  并发性控制的重要性

  如果没有控制并发性的有效方法,就可能损害数据的完整性和一致性。DBMS 必须保护数据库,防止发生下列状况:

  • 丢失更新—— 假设应用程序 A 和应用程序 B 同时读取数据库中的同一行,并且都为其中某一列计算新值。如果应用程序 A 先用其新值更新该行,随后应用程序 B 又更新同一行,那么第一次的更新(由应用程序 A 执行的)就会丢失。
  • 不可重复读—— 某些应用程序进程可能要求完成以下事件序列:程序 A 从表中读取特定的一行,然后继续进行其他的 SQL 请求。稍后,程序 A 再次读取开始的那一行,并且必须在所有的列中找到与第一次读取相同的值。如果缺乏合适的并发性控制,另一应用程序就可能在这两次读取操作之间修改该行数据。
  • 访问未提交的数据—— 应用程序 A 更新一行中的某些列的值,而在提交该修改之前,应用程序 B 读入该行的新(更新)值。如果应用程序 A 接着又“撤销”更新值(通过程序逻辑中的 SQL ROLLBACK 语句,或者因为发生错误由 DB2 UDB 自动进行回滚),那么,应用程序 B 对该行的处理就是基于未提交的(因而可能是不正确的)数据进行的。

  在维护数据完整性的同时,提供多个应用程序同时访问数据的能力称作 并发性控制

  

  锁是一种由 DB2 UDB 用于完成并发性控制的软件机制。锁实质上就是一个控制块,将 DB2 UDB 对象或资源与应用程序关联起来,并控制其他应用程序如何访问同一对象或资源。与 DB2 UDB 资源有关联的应用程序被称为“持有”或“拥有”该锁。

  通过使用锁,DB2 UDB(管理该数据库)可以防止发生上述几类问题。DB2 UDB 与另一 MVS 地址空间 IRLM 配合管理这些锁。IRLM 将跟踪这些锁及其所有者,以确定应用程序请求的 DB2 UDB 资源是否可用于该类工作。资源可以是 锁定的共享的,这取决于当前资源上的锁的“持有者”所进行的处理类型,以及请求应用程序所预期的处理类型。

  锁模式

  最常用的两种锁模式是 共享的排他的。共享锁与只读操作有关联,这意味着持有该锁的应用程序可以读取数据,而其他应用程序也可以读取该数据。排他锁与写操作有关联,这意味着持有该锁的应用程序有资格更新数据,但在锁所有者完成更新(将修改提交给数据库)并释放该锁之前,其他应用程序无法使用该数据。

  DB2 UDB 和 IRLM 使用其他类型和子类型的锁模式来实现锁定和并发性控制。您可以在 DB2 UDB Administration 手册中找到关于这点的更多详细描述。

  锁定粒度

  除了使用各种锁模式,DB2 UDB 还提供了不同的锁定级别,用以控制被锁定数据的范围。各种级别表示了 DB2 UDB 使用的锁定粒度,其范围可以从单个行到整个表空间。DB2 UDB 根据锁定粒度来使用不同的锁模式。

  具有多种锁定级别的理由十分简单。某些应用程序可能要求有权读取或更新大范围的数据,而其他应用程序则可能只要求访问窄得多的范围。如果只能使用一种锁定级别,则会降低整个系统性能。例如,一下子锁定太多数据会强制其他应用程序进行不必要的等待。否则,DB2 UDB 可能使用过多的系统资源,尝试服务对附加数据资源进行锁定的附加请求。能够实现多种级别的锁粒度可以极大地提高并发性水平。

  暂挂

  若一个应用程序进程请求一个锁,而该锁已被另一应用程序进程所拥有,且不能共享,此时,就称该进程为 暂挂的。请求应用程序会被挂起,即它将暂时停止运行。锁请求的优先次序如下:将新来的锁请求按照接收次序进行排队。已经持有锁的应用程序的请求以及进行锁提升的请求要比新应用程序的请求先得到服务。而在那些分组中,请求次序则为“先进先出(first in,first out,FIFO)”。

  超时

  当应用程序处于暂挂状态(见上面)超过了预设的一段时间间隔,那么就要终止该应用程序。该应用程序被称为已经超时。在终止该应用程序之前,会在 SQLCA 中收到一条合适的错误消息。SQLCA(SQL 通信域)是 SQL 应用程序预留的一块大小固定的存储区域,用于从 DB2 UDB 向程序传递条件代码和其他信息。

  某些操作,如 COMMIT 和 ROLLBACK,就不能超时。在下面的子标题 RESOURCE TIMEOUT 中,将对决定应用程序进程可以等待资源多长时间的预设时间间隔进行讨论。

  死锁

  当两个或更多应用程序每个都持有另一应用程序所需资源上的锁,没有这些资源,那些应用程序都无法继续完成其工作时,这时就会出现死锁的状况。

  以下是一个简单的死锁场景:

  1. 应用程序 A 访问表 T,并请求页面 X 上的排他(非可共享的)锁。
  2. 应用程序 B 访问表 T,并请求页面 Y 上的排他锁。
  3. 然后,应用程序 A 请求页面 Y 上的锁,同时仍然持有页面 X 上的排他锁。应用程序 A 将被挂起,因为应用程序 B 具有页面 Y 上的排他锁。
  4. 然后,应用程序 B 请求页面 X 上的锁,同时仍然持有页面 Y 上的排他锁。应用程序 B 将被挂起,因为应用程序 A 具有页面 X 上的排他锁。
  5. 这是一种僵持情况。应用程序 A 和 B 都无法继续工作。

  在一段预设的时间间隔之后(请参阅标题 DEADLOCK TIME 下面的讨论),DB2 UDB 将终止当前工作单元,因为某个应用程序陷入死锁状态(通常为所做工作最少的应用程序)。这将释放终止程序所持有的锁,并允许剩余的应用程序继续下去。 DB2 UDB 将向被终止的应用程序的 SQLCA 发送描述性的错误消息和信息。

  实用程序和命令的并发机制

  当 SQL 应用程序使用事务锁来控制对 DB2 UDB 对象的并发访问时,DB2 UDB 实用程序和命令可以通过其他方法访问 DB2 UDB 对象,即 声明(claim)、放弃(drain)兼容性规则。

  声明是指通知 DB2 UDB 当前正在访问某个特定对象。提交之后,该声明通常不会继续存在。为了在下一工作单元里访问 DB2 UDB 对象,应用程序需要进行新的声明。声明通知 DB2 UDB 当前正关注某个 DB2 UDB 对象,或是该对象上存在活动。只要 DB2 UDB 对象上存在声明,在释放那些声明之前,就不能采取任何放弃(drain)。

  放弃(drain)是指通过下列方式来访问 DB2 UDB 对象的动作:

  1. 阻止对对象进行任何新的声明。
  2. 等待释放对象上现有的所有声明。

  DB2 UDB 对象上的放弃导致 DB2 UDB 停止(quiesce)所有当前正声明该资源的应用程序,其方式为允许它们到达提交点,但阻止它们(或任何其他的应用程序进程)进行新的声明。Drain 锁还阻止冲突进程同时放弃(drain)同一对象。

  DB2 UDB 一般通过一组兼容性规则来控制实用程序的并发操作。如果两个实用程序不需要同时以不可兼容的模式访问相同的 DB2 UDB 对象,就将它们视为是“兼容的”。当一个实用程序作业开始时,DB2 UDB 就检查系统,查看当前是否有其他任何实用程序正处理同一 DB2 UDB 对象。如果该对象当前未被另一实用程序访问,或者如果另一执行实用程序是可兼容的,该实用程序才可以继续。



  数据库设计考虑

  取得高度并发性应该是指导 DB2 UDB 数据库初始设计的目标之一。在创建各个表的同时,可能要考虑许多影响并发性的功能。您可以在实现创建之后再添加某些功能(例如通过更改 DB2 UDB 对象),但另一些功能则无法添加,或者至少需要大量重复操作和/或打乱当前实现。因此,最好是一开始就考虑这些问题。

  分区

  我极力建议您将较大的表创建为分区表。一个分区表空间只包含单个分区表。要基于分区索引的键范围将该表分成多个分区。每个分区都是作为单独的数据集(dataset)来创建的,并且可以作为单独的实体由 DB2 UDB 加以处理。

  对于大量的批处理操作(INSERT、UPDATE、DELETE),您可以将大型作业划分成较小的作业,利用该分区表结构。许多这些较小的作业可以并发运行(对不同的分区进行),以便减少整个批处理操作的占用时间,从而使另一应用程序进程可以更早地访问该表。

  分区表以类似的方式使实用程序作业只作用于所选择的分区,从而允许其他作业或应用程序进程并发访问表中的其他分区。在许多情况下,DB2 UDB 实用程序本身就具有利用分区表的能力,而在其他情况下,它只为用户提供了设计其工作负载的选项,以便支持 DB2 UDB 数据的更高并发性级别。

  锁升级

  DB2 UDB 使用升级技术来平衡锁定性能开销的并发性需求。当一个应用程序进程持有单个表或表空间上的大量页面锁、行锁或 LOB 锁时,DB2 UDB 就获取该资源上的表或表空间锁,然后释放该资源上以前的页面锁、行锁或 LOB 锁。该过程称作 锁升级

  如果一个使用分区锁定(带有 LOCKPART YES 的 CREATE 或 ALTER)的表上发生锁升级,那么,就只升级当前被锁定的分区,未锁定的分区仍旧未锁定。一旦表空间中发生了锁升级,那么就要用表空间锁来锁定随后访问的未锁定分区。

  在执行应用程序时,DB2 UDB 首先使用页面锁或行锁,并且只要该进程访问相对较少的页面或行,还会继续这样做。当该应用程序访问许多页面或行时,DB2 UDB 将变为使用表锁、表空间锁或分区锁。调用锁升级的准确时间是由 LOCKSIZE 和 LOCKMAX.的值决定的。

  LOCKSIZE

  LOCKSIZE 是 CREATE/ALTER TABLESPACE 语句的选项,在应用程序进程访问表空间中的表时,控制 DB2 UDB 获取的何种类型的锁(即,它决定该锁的“大小”,这有时也称作锁粒度)。该选项的可以是 LOB、TABLESPACE、TABLE、PAGE、ROW 和 ANY。

  CREATE TABLESPACE 语句的 LOCKSIZE 参数默认为 ANY。LOCKSIZE ANY 允许 DB2 UDB 选择锁大小。DB2 UDB 通常将 LOCKSIZE PAGE 用于非 LOB 的表,而将 LOCKSIZE TABLESPACE 用于 LOB 表。

  我建议在创建表空间时使用该默认值,除非您有理由进行其他选择。如果您选择修改 LOCKSIZE,那么就要根据使用该表空间的应用程序的性能监控结果和并发性特点来做决定。

  使用何种大小的锁

  在 DB2 V4 中才开始可以使用行级锁。之前,数据页是最小的锁定单元。I/T 行业中的许多人都假定行锁是并发性问题的灵丹妙药,但实际上,它并不能解决所有的并发性问题。在经历许多锁等待、死锁和超时的环境中,它也许提供了较大的改进。但在其他情形下,DB2 UDB 可能在获取更多锁上消耗资源,同时无法成比例地提高并发性。

  因为 IRLM 获取、维护和释放行锁所需的处理与页面锁需要的大致相同,所以关于指定锁大小的决定其实就是在较高的锁定开销与并发性的潜在提高之间进行权衡。

  因此,至于是使用页面锁还是使用行锁,这取决于您的数据和应用程序的特点。如果您觉察到使用页面锁定级别的表空间的数据页上存在大量竞争,那么就请考虑使用行锁。通过在行级别而非页面级别上进行锁定,可以极大地减少与其他应用程序进程的竞争,特别在访问是在随机的情况下。

  但是,如果多个应用程序正以不同的顺序更新某一页上的相同行,那么行锁导致的竞争甚至可能比页面的还要多。这是因为,通过页面锁,第二个以及随后的应用程序在访问该页面之前,都必须等待第一个应用程序完成,而它们就可能超时。通过行锁,多个应用程序可以同时访问同一页上的行,但如果它们试图访问相同的行集,就可能死锁。

  使用 LOCKSIZE TABLESPACE 或 LOCKSIZE TABLE 之前,用户必须相当确定没有其他重要的应用程序进程需要并发访问该对象。在任何表上指定 LOCKSIZE ROW 之前,极为明智的做法就是先对增加锁开销来提高并发性是否值得进行估计。

  小型表

  如果一个 DB2 UDB 表兼具尺寸小和高使用率的特点,那么就考虑使用 LOCKSIZE ROW,特别是每个页面中有许多行的时候。此外,因为表十分小,锁粒度将不会给锁开销带来较大的性能影响。

  DB2 UDB 对象和授权的分组

  通常总是将与同一应用程序逻辑相关的表分组到一个数据库中。理想情况是,每一个应用程序进程都引用尽可能少的 DB2 UDB 数据库。而且,要设法给用户不同的授权 ID 来使用不同的 DB2 UDB 数据库。实际上,这将增加可能应用程序进程的数目,同时可能减少每个应用程序进程可以访问的数据库数目。因此一般说来,“组合相似的事物”和“分开不同的事物”。



  应用程序设计考虑

  正如初始数据库设计一样,早在开发新的 DB2 UDB 应用程序之时就开始考虑并发性问题也很重要。更好的做法是,在实现之前,就将并发性原则包含到生产中去。然而,有时候并发性的提高可能取决于较新的 DB2 UDB 版本中的新功能以及与之接合的其他系统。在这些情况下,可能需要修改现有的应用程序,以便提高并发性水平。而在其他情况下,可能要等并发性问题发展和增长到一定的时候,才有必要对应用程序进行大量修改。以下是一些用以评估在某一时刻是开发新的 DB2 UDB 应用程序,还是修改现有的 DB2 UDB 应用程序的选项。

  尽可能晚地访问关键资源

  例如,设计和编写应用程序,以便 SQL 语句获取和更新 DB2 UDB 数据的时间尽可能地接近提交点。注意该问题将减少锁定数据的时间总量,从而减少其他应用程序进程无法使用该数据的时间。

  尽可能快地提交工作

  通常,在达到数据一致性临界点之后,尽快发出 COMMIT 语句是一种较好的 SQL 编程技术。该实践将有助于避免不必要的锁竞争,甚至在只读应用程序中也应该采用该实践。类似地,我们建议应用程序中一检测出 SQL 故障,就发出 ROLLBACK 语句。这将阻止未成功的 SQL 语句不必要地、过长时间地锁定 DB2 UDB 资源。

  尽可能快地关闭游标

  尽可能快地关闭游标是另一个可以帮助提高并发性的编程实践。通过尽快发出 CLOSE CURSOR 语句,应用程序将释放那些锁以及它们所持有的 DB2 UDB 资源。

  应用程序重试逻辑

  我建议应用程序中包含重试逻辑,即当程序接收死锁或超时状况的指示时,该应用程序代码应重试该操作,甚至允许重试多次。这就使得应用程序有可能从该状况中恢复,而无需操作人员从外部干涉或提供帮助。SQLCA 中的字段 SQLERRD(3) 返回一个理由码,指示是否发生了死锁或超时。

  以一致次序访问数据

  在多数环境中,时常会发生不同的应用程序需要访问同一数据的情况。要在您的环境中规定应用程序以相同的次序访问数据。例如,要求所有应用程序按升序访问表 XYZ 中各行。如果那样,第一个访问表 XYZ 的应用程序就可能会延迟其他应用程序,但不会导致死锁的状况。以类似的方式,所有访问表 A、B 和 C 的应用程序都应按字母顺序来进行访问。

  增加联机并发性

  减少死锁和超时的一种方法就是按照上面所描述的,在整个系统中按照一致次序来访问 DB2 UDB 数据。而第二种方法则是在 UPDATE 和 DELETE 上使用 WHERE CURRENT OF 游标选项,而不是使用独立的 UPDATE 和 DELETE 语句。在争取获得联机 DB2 UDB 数据的最大并发性时,考虑的第三种选择就是在非活动或非高峰 OLTP 时期,尝试为联机表计划批处理活动。

  参照约束

  在 DB2 UDB 数据库中使用参照约束可以带来性能影响。父表中某行上的 DELETE 语句需要获取子表(或者,至少是其索引)上的锁。那么,那些子表的并发使用的可用性会由于那些锁而减小。该影响对于级联删除可能相当大,因为访问单行的单个删除操作可能需要扫描多个表中的多行数据。

  您经常可以通过在外键上创建索引来减小性能影响。虽然并不强制外键上有索引,但是若不存在该索引,那么当从父表中删除一行时,DB2 UDB 就必须扫描整个子表。

  可以潜在地改进该状况的另一个动作必须与组合相关表有关。如果频繁删除父表中的行,则可以考虑将所有的子表(以及它们的子表)置于同一表空间中。

  BIND 选项

  将“源” SQL(实际上就是准备好的 DBRM)转换成可执行代码的过程称作绑定,在某些方面类似于 COBOL 等过程语言的编译/链接-编辑过程。在绑定 SQL 代码时,可以指定许多选项,而这些选项可以极大地影响应用程序将经历的并发性程度。下面讨论了一些影响并发性的重要选项。

  • ACQUIRE

  参数 ACQUIRE 决定 DB2 UDB 用以获得应用程序所请求数据上的锁的时间。ACQUIRE(ALLOCATE) 指示 DB2 UDB 在分配应用程序计划时获取所有所需的表空间锁,但 ACQUIRE(USE) 将使 DB2 UDB 等到应用程序首次向该表发出 SQL 语句时,才获取那些锁。

  您通常应该用 ACQUIRE(USE) 选项(这是包绑定的默认选项)来绑定计划。那通常也是并发性的非常好的选择。ACQUIRE(ALLOCATE) 选项可以更好地保护优先级极高的作业免于死锁。为了偶尔适当地使用该选项,您可能需要考虑将所有 DBRM 直接绑定到该计划。

  • RELEASE

  DB2 UDB 释放它所持有的锁的时间是由 RELEASE 选项决定的。通过 RELEASE(COMMIT),DB2 UDB 指示 IRLM 在到达 COMMIT 点时释放锁,而通过 RELEASE(DEALLOCATE),DB2 UDB 将保留所有锁,直到终止程序并释放计划。

  虽然不一定适用于所有状况和环境,但通常的建议是将 RELEASE(COMMIT) 用于 OLTP 应用程序,以及其他请求相对少量锁和应用程序进程的经过时间相当短的应用程序。对于批处理应用程序进程以及其他获取大量锁且/或执行时间长的应用程序进程来说,RELEASE(DEALLOCATE) 通常为更好的性能选项。

  ACQUIRE/RELEASE 组合

  对于 BIND 的 ACQUIRE/RELEASE 选项的通用建议是基于应用程序的特点选择组合值。有一种组合,即 ACQUIRE(ALLOCATE)/RELEASE(COMMIT) 是不允许的。DB2 BIND 进程会将该种说明标记为错误。其他三种可能的组合都有其合适的用法,下面将简要地进行描述。

  • ACQUIRE(ALLOCATE)/RELEASE(DEALLOCATE)

  ACQUIRE(ALLOCATE)/RELEASE(DEALLOCATE) 可能对于运行几小时并访问不同表的批应用程序十分有用,因为该组合将有效地阻止死锁状况损耗一切处理。一开始运行程序,应用程序就会锁定所有所需资源。

  该组合的不足就是它可能严重地减小并发性。 潜在地具有较高需求的资源的锁定时间可能比实际所需要长。同时,请注意 ACQUIRE(ALLOCATE) 禁用选择性的分区锁定,以至于如果您访问用 LOCKPART YES 定义的表空间,将会锁定所有分区。

  • ACQUIRE(USE)/RELEASE(DEALLOCATE)

  ACQUIRE(USE)/RELEASE(DEALLOCATE) 组合通常导致最为高效地使用处理时间。DB2 UDB 资源仅在应用程序需要它们时才被锁定。这样就减小了锁开销,因为在那之后,会将所获取的锁保留到终止应用程序之时。

  该组合的不足就是相较于 ACQUIRE(ALLOCATE) 时,它可能导致更频繁地发生死锁状况。只有在实际的应用程序运行时,才按可预测的次序这样来作用所获取的锁。因此,可能发生更多死锁。

  • ACQUIRE(USE)/RELEASE(COMMIT)

  ACQUIRE(USE)/RELEASE(COMMIT) 碰巧就是 BIND 的默认组合,提供了非常好的的并发性。DB2 UDB 资源仅在应用程序需要它时才被锁定,如果该应用程序包含许多不常使用的 SQL 语句,这就可能极其重要。

  然而,其权衡就是,如果应用程序相当频繁地提交,就可能增加处理时间,那么该应用程序就必须再次重新获取锁。与上面的 ACQUIRE(USE)/RELEASE(DEALLOCATE) 组合一样,也可能增加死锁,因为只有在执行实际的程序时,才按可预测的次序获取所有的锁。

  ISOLATION

  参数 ISOLATION 决定 DB2 UDB 为保护一个应用程序所用数据,防备该 DB2 UDB 系统里的其他应用程序,而维护的保护或隔离程度。下面完全从并发性角度出发,按照递增的优先级列出了 4 个选项:

  1. ISOLATION(RR)

      通过可重复读(Repeatable Read,RR),要用行锁或页面锁锁定应用程序所访问的所有行(无论是否为限定的),并至少保持到下一提交点。DB2 UDB 确保:

    • 您的应用程序不读取另一应用程序进程修改的那一行,直到该进程释放该行,并且
    •   其他应用程序进程不修改您的应用程序所读取的那一行,直到应用程序进行了提交或终止。

  2. ISOLATION(RS)

      通过读稳定性(Read Stability,RS),要用行锁或页面锁锁定所有返回给应用程序的限定行,并至少保持下一提交点。DB2 UDB 确保:

    • 您的应用程序不读取另一应用程序进程修改的那一行,直到该进程释放该行,并且
    •   其他应用程序进程不修改满足您应用程序的搜索规则(由 WHERE 从句决定)的那一行,直到应用程序进行了提交或终止。它允许其他应用程序进程 INSERT 一行或 UPDATE 现有的行,只要该行原来不满足其搜索规则。

  3. ISOLATION(CS)

      通过游标稳定性(Cursor Stability,CS),仅仅将行锁或页面锁保留到游标移动到另一行或另一页。因此,DB2 UDB:

    • 确保您的应用程序不读取另一应用程序进程修改的那一行,直到该进程释放该行,但是
    •   允许其他应用程序进程在您的应用程序进行了提交或终止之前,修改您的应用程序所读取的那一行。

  4. ISOLATION(UR)

      通过未提交读(Uncommitted Read,UR),DB2 UDB 不获取任何行锁或页面锁(除了对于 LOB 数据),且可以与大多数其他操作并发运行。在 I/T 行业中,这常常称作“脏读”。通过该隔离级别,DB2 UDB 允许:

    • 其他应用程序进程修改您的应用程序在工作单元期间可能读取的行。
    • 您的应用程序读取另一应用程序进程所修改的那一行,即使该修改还未被提交。

  CURRENTDATA

  该选项仅适用于 ISOLATION(CS)。对于与游标稳定性(Cursor Stability)绑定的应用程序中的只读和歧义游标,该选项决定是否需要数据并发性。换言之,它告诉 DB2 UDB 该游标所定位数据是否必须与基表中的数据保持一样(或“相容”)。进一步需要阐明的是,对于歧义游标,DB2 UDB 无法确定是将它用于更新还是将它用于只读目的。

  CURRENTDATA(YES) 指定需要数据并发性,以致 DB2 UDB 将获取页面锁或行锁来确保这一点。CURRENTDATA(NO) 指定不需要数据并发性,因此,DB2 UDB 不会获取那些锁。

  重写隔离(WITH 从句)

  DB2 UDB 允许应用程序里的 SQL 语句指定所绑定计划或包的隔离级别。这将有效地重写用于计划或包的隔离级别,但仅仅是在它出现的语句中。WITH 从句可以用于下列类型的 SQL DML 语句:

  • SELECT
  • “搜索的”UPDATE 和 DELETE
  • 子查询中的 INSERT

  ISOLATION 建议

  合适的 ISOLATION 选项需要通过应用程序的业务逻辑和数据完整性需求来决定。该决定需要对应用程序需求和相关的业务规则具有基本理解。

  通常,您应该使用最少的限制隔离,它仍将维护应用程序的数据完整性需求。隔离的限制越少,数据的并发性就越高,因而访问 DB2 UDB 数据的性能就越好。通常,ISOLATION(CS) 与 CURRENTDATA(NO) 将是一个较好的起始点。ISOLATION(CS) 允许 DB2 UDB 尽快释所获取的锁,而 CURRENTDATA(NO) 允许 DB2 UDB 避免过于频繁地获取锁。

  未提交读(Uncommitted Read,UR)极其高效,仅导致少量竞争或不导致竞争,但是,它的使用应极为谨慎,因为它使 DB2 UDB 能够读未提交的数据。一般不推荐使用 ISOLATION(UR),除非已经确定应用程序和终端用户可以接受 UR 所允许的逻辑数据不一致性。

  锁避免和隔离

  锁避免是在 DB2 V3 中引入的并发性提高。它使 DB2 UDB 在确定某页中的数据已经提交之后,就能够读取该页面,而无需获得锁。是否可以使用锁避免取决于应用程序在其 SQL 调用中使用的隔离程度。

  ISOLATION(RR) 和 ISOLATION(RS) 不允许任何锁避免。带有 ISOLATION(CS) 的游标对非限定的页面或行使用锁避免。带有 ISOLATION(CS) 和 CURRENTDATA(NO) 的歧义游标和只读游标对限定的和非限定的页面或行均使用锁避免。

  限制表空间上的锁

  LOCKMAX 是一个可以在 CREATE 或 ALTER 表空间时指定的选项。这指定在进行锁升级之前,单个应用程序可以持有的 DB2 UDB 表空间上的页面锁或行锁的最大数目。该选项可以同时对用户数据和 DB2 UDB 编目生效。

  如果 n表示发生锁升级之前页面锁或行锁的最大数目,那么就指定 LOCKMAX n。通过指定 LOCKMAX 0 可禁止锁升级。指定 LOCKMAX SYSTEM 是将最大数目设置为系统默认值,而系统默认值是由安装面板的 DSNTIPJ 上的选项 LOCKS PER TABLE(SPACE) 设置的。

  限制应用程序获取的锁

  DB2 UDB 还允许设置单个应用程序持有的页面锁或行锁的最大数目。该选项 LOCKS PER USER 也位于安装面板 DSNTIPJ 上。如果一个应用程序进程已经持有所指定的最大数目,但又向 IRLM 请求一个页面锁或行锁,那么 DB2 UDB 就会向该应用程序的 SQLCA 发送一条错误消息。直到释放某些现有的锁之后,才可以获取所请求的锁。

  默认的 LOCKS PER USER 为 10,000。当使用页面锁时,该数字对于大多数装置的多数工作负载来说通常已经足够了。对于极其大型的表上的行级锁或 LOB 来说,可能有必要指定一个更大的值。或者,您可能需要查看需要更大值的应用程序类型,并分析它们是否可以使用表空间锁来代替页面锁、行锁或 LOB 锁。



  系统和安装考虑

  除了上述关于数据库和应用程序设计的建议之外,这里还有一些关于安装选项和系统环境的建议和考虑:

  DB2 UDB 编目竞争

  某些 SQL DDL(数据定义语言)、SQL DML(数据操作语言)和 SQL DCL(数据控制语言)语句获取 DB2 UDB 编目上的锁。如果多个应用程序进程都发出这些语句,就可能真正发生编目竞争。

  我建议您尝试最低限度地并发使用更新同一 DB2 UDB 编目表空间的语句。通常,需要设置关于何时应提交这种 SQL 的标准,并且/或者限制可以更新该 DB2 UDB 编目的授权用户 ID 数目。关于可能导致编目竞争的 DDL、DML 和 DCL 的详细描述,请参阅 DB2 UDB Administration Guide。

  IRLM 优先权

  应该给予 IRLM 极高的 MVS 调度优先权,以便它可以尽快为锁请求提供服务。IBM 通常建议 IRLM 的优先权仅次于 VTAM 的,但要高于其他 DB2 相关的地址空间。

  IRLM 存储

  面板 DSNTIPJ 上的 CROSS MEMORY 条目决定了 IRLM 是否在 ECSA(扩展的控制存储区)或它自己的私有地址空间里存储其锁控制块。

  指定 NO(PC =NO,这意味着不使用跨地址空间的程序调用)会将锁结构置于 ECSA 中,而且只需要较少的 CPU 时间,但是,却可能减少使用 ECSA 的其他 MVS 地址空间可用的存储器数量。通过该选项,另一参数 MAXIMUM ECSA 也将起作用。这将决定 IRLM 可用于其锁结构的 ECSA 的最大数量。重要的是,要将该值设置得足够高,以防止 IRLM 到达极限。IRLM 仅仅按需获取存储器,因此,较好的是设置一个比估计需要值要大的值。

  通过指定 YES,锁控制块结构将存储于 IRLM 私有地址空间中,而程序调用指令(PC=YES)则用于处理该结构。如果有 PC=YES,则会忽略 MAXIMUM ECSA 选项。

  目前,DB2 UDB V7 的经验显示 PC=YES 为推荐设置,因为虚拟地址空间约束的影响被认为比通过选择该选项而消费的附加 CPU 周期产生的影响更大。从 DB2 UDB V8 开始,就不再使用 PC 和 MAXIMUM ECSA 键(V8 自动实现 PC=YES)。

  MVS 系统负载和交换

  如果您的 MVS 系统资源由于工作负载过重而较为紧张,那么 CPU 周期、I/O 处理和存储器上增加的竞争则可能导致任务陷入等待状态,直到可以获得所需的资源。

  同样明智的是,在您的 MVS 系统中根据实际情况减少“交换”量。当 MVS 分页率变得过高时,某一地址空间就可能被“换出”,即将地址空间中的所有虚拟存储器移至磁盘中。每当一个任务被换出,或等待其他系统资源,而该工作单元还未提交,那么该任务就继续持有 DB2 UDB 对象上的锁,这将影响并发性。

  DEADLOCK TIME

  参数 DEADLOCK TIME 指定 DB2 UDB 在系统中扫描死锁的应用程序进程的时间间隔长度。该参数是在安装面板 DSNTIPJ 上指定的,其默认值为 5 秒。

  正如所期望的,死锁检测的非常好的值的确定取决于您 DB2 UDB 应用程序工作负载的特点和大小。因为死锁检测可以导致锁存器暂挂,所以如果您的系统未经历死锁,那么可以考虑使用默认值。死锁检测的检测不那么频繁则意味着 DB2 和 IRLM 所花的处理时间将会少一点。但是,如果您经历了一定程度的死锁,则应该将该值减小至 1 秒(您需要尽快发现死锁情况)。

  RESOURCE TIMEOUT

  参数 RESOURCE TIMEOUT 是在安装面板 DSNTIPI 上指定的,它规定了在发生超时之前 DB2 UDB 的最小秒数。默认值为 60 秒,这可能是新的 DB2 UDB 安装的较好起始点。因为您应用程序的性能特点会随时间发展并改变,所以您需要评估该参数值是否有效地维护较好的并发性。较小的值可能导致更多的超时。通过设置一个较大的值,暂挂的应用程序通常会更为频繁地重新开始,但是在更长的期间内,它们仍然是非活动的。

  此外,这里有一个虚拟场景,演示了如何可以减小该值来真正减少超时,即使该思想与开始的直觉相反。假设 RESOURCE TIMEOUT 为 60 秒。

  1. 事务 A 得到页面 1 上的锁,但是随后陷入等待状态,因为它请求页面 2,而页面 2 当前正被事务 X 锁定。
  2. 事务 B 在 1 秒钟之后启动,得到页面 3 上的锁,但是随后陷入等待状态,因为它请求页面 1。
  3. 事务 C 在 1 秒钟之后启动,得到页面 4 上的锁,但是随后陷入等待状态,因为它也请求页面 1。
  4. 1 秒钟之后,事务 D 和 E 分别请求页面 3 和 4,并且陷入等待状态。
  5. 事务 A 等待了 59 秒,然后最终获得了对页面 2 的访问,但是它在结束处理之前,还要继续持有页面 1 上的锁 5 秒钟。
  6. 因此,其他所有 4 个事务 B-E 都超时了。

  如果 RESOURCE TIMEOUT 为 30 秒,那么事务 A 在获得页面 2 之前就会超时,但这样可以释放页面 1,以致事务 B 到 E 都可以执行。因此,超时的事务将不是 4 个,而是只有 1 个。

  禁止非活动的应用程序

  安装面板 DSNTIPR 上有一个 IDLE THREAD TIMEOUT 选项。该选项指定不进行任何处理时,活动 DB2 UDB 分布式线程可以持有锁的时间期。在该时期之后,DB2 UDB 扫描进程就检测出该线程已经空闲了一段指定的时间,因此,DB2 UDB 就取消该线程。

  RELEASE LOCKS

  参数 RELEASE LOCKS 位于安装面板 DSNTIP4 上,控制在进行提交时,是否释放被定义为 WITH HOLD 的游标使用的页面锁或行锁。默认值为 YES,这意味着 DB2 UDB 在发出 COMMIT 之后,释放页面锁或行锁。您应尽可能多地使用该默认值,因为这将提高并发性。

  如果用户指定 NO,那么 DB2 UDB 将在发出 COMMIT 之后,为 WITH HOLD 游标保留该页面锁或行锁。该选项允许当前依靠该锁的应用程序可以像之前一样继续工作。



  操作考虑

  除了上面提及的关于数据库和应用程序设计考虑的众多事项之外,还有一些与 DB2 UDB 日常操作有关的事情,这些操作可以影响数据并发性水平,特别是在 DB2 UDB 实用程序区域中。

  为实用程序提高并发性

  许多 DB2 UDB 实用程序都由于 NPI(非分区索引),受益于分区之间所提高的数据并发性。为了实现这一提高,您需要通过分区并行地运行实用程序,由于并发地执行实用程序,这可能需要新的操作和组织考虑。

  LOAD 和 REORG 实用程序都可以极大地从使用该技术中受益。但是,分区依赖性并不有益于 COPY 和 MODIFY 实用程序,因为它们均工作于数据级。

  联机 REORG

  在 DB2 UDB Version 5 之前,由 REORG 实用程序处理的数据访问要遭受多得多的限制。在 REORG 的卸载阶段,其他应用程序受到限制,只能对该表空间进行只读访问,而在再装入阶段,根本不能对该表空间进行访问。在 DB2 UDB V5 中获得 REORG 实用程序增强之后,通过指定 SHRLEVEL 选项,极大地提高了数据的可用性。这称作“联机”REORG。

  通过在 REORG 实用程序作业中指定 SHRLEVEL REFERENCE,其他应用程序几乎在整个作业时期中都可以只读访问该数据。在 REORG 中指定 SHRLEVEL CHANGE 将允许其他应用程序几乎在整个重组过程中读/写访问该数据。

  就在该实用程序完成之前,有一段极短的时期将拒绝数据访问。多数客户经常利用联机 REORG 来使得其他应用程序可并发地使用关键的业务数据,并且同时执行必要的数据维护。通过联机 REORG 实用程序,DB2 UDB 为用户提供了虚拟连续可用性方面的极大进展。

  减少 REORG 期间的死锁

  在调用联机 REORG 时,您可能会经历死锁。如果真的发生了,那么您应该运行带有 DRAIN ALL 选项的 REORG 实用程序。DRAIN WRITERS 是默认的选项,在日志记录阶段执行。DRAIN ALL 选项指示 DB2 UDB 一到达 MAXRO 阈值,就同时放弃(drain)写入者和读取者。在日志记录阶段经历过较大更新活动的环境中,应特别考虑该方法。若指定了 DRAIN ALL,那么在联机 REORG 的切换阶段,就无需要进行放弃(drain)。



  监控锁定

  您应知道您的 DB2 UDB 系统所具有的数据并发性水平。应用程序或数据库设计方面的不足、受约束的硬件资源、渐增的事务量、数据库增长和其他因素都可以导致性能和并发性的降低。

  首先,要奠定“基线”- 换言之,就是要奠定在您的环境中用于应用程序和数据库的标准。监控量(volume)以及异常或错误条件成为识别和解决并发性问题的关键。

  显示实时的锁信息

  通过在发出 DISPLAY DATABASE 命令时包含 LOCKS 选项,DB2 UDB 将在特定时刻及时地显示大量关于持有或等待哪些锁的信息。对于显示(DISPLAY)的 DB2 UDB 数据库(或是其中的一部分)来说,该信息包括:

  • 用于所有表、表空间、分区和索引空间(indexspace)的事务锁,以及这些锁的模式和持续时间。
  • 当前持有、等待或保留锁的应用程序和 DB2 子系统。
  • 用于分配给数据库的所有应用程序的授权 ID、相关 ID 和连接 ID 的信息。
  • 关于逻辑分区和当前执行的实用程序作业所持有的其他资源的 drain 锁信息。

  决定锁模式

  如果发出 EXPLAIN 语句,或者如果在 BIND 和 REBIND 命令中包含 EXPLAIN 选项,就可以决定表和表空间锁的模式(Share、eXclusive 等),而这些锁最初是 DB2 UDB 为特定的 SQL 语句指派的。EXPLAIN 的输出存储在 PLAN_TABLE 中,用户然后可以进行查询。表空间的初始锁模式包含在 PLAN_TABLE 的 TSLOCKMODE 列中。

  跟踪锁信息

  为了最大化环境中的数据并发性,回答下列问题将有助于识别模式、趋势和问题:

  • 哪些应用程序竞争资源?
  • 对于哪些表和/或表空间来说,存在竞争?
  • 竞争的类型和频率是什么?
  • 那些竞争有何影响?

  您应运行统计(Statistics)、记帐(Accounting)和跟踪(trace)功能来记录关于 DB2 UDB 应用程序性能特点以及整个 DB2 UDB 系统的信息。这些跟踪应该连续运行,这样您就可以定期创建报表来查看和分析数据。这项报表编制功能可以用 IBM 的 DB2 Performance Expert(DB2/PE)或类似的程序来完成。

  统计(Statistics)跟踪记录报表时间间隔中发生的锁定和解锁请求、声明和放弃请求、锁暂挂、死锁、超时和锁升级的数目。记帐(Accounting)跟踪记录在应用程序(而非³»统)级别上跟踪相同类型的信息。此外,这些跟踪显示了整个时间间隔期间,以及持有和获取的并发页面锁的最大数目。

  从并发性观点来看,您应该查看这些报表来帮助您识别下列状况:

  • 锁升级通常视为是不理想的,一般是由持有大量页面锁、行锁和 LOB 锁的应用程序导致的。对于这些情况,通过一开始就使用表锁或表空间锁可以提高并发性和整个系统性能,因此,要避免升级发生。
  • 超时可能是由一个对于您的环境来说并非非常好的的 RESOURCE TIMEOUT 值导致的。如果存在大量超时,则可能是该值过低。较高的 RESOURCE TIMEOUT 值可以为更多应用程序提供完成其工作的机会。但是,要密切监控您所做的修改,因为通过减小该值(请参阅子标题 RESOURCE TIMEOUT 下面的讨论),可以改善某些状况。
  • 死锁可能是性能和并发性的主要障碍。通常应进一步调查这些东西,以便同时确定导致障碍的原因以及可能的解决方案。
  • 某些应用程序一贯地持有大量锁。请查看这些应用程序(以及底层的数据库设计),以判断是否可以降低该数字。

  理想情况是,装置争取获得死锁和超时的“零”计数,最多具有少量的锁升级。如果在经过有限的少量事件之后,发生了这三种征兆之一,那么可能应该调优您的锁定了。

  关于锁升级,一条经验法则主张如果有超过四分之一的锁升级最终导致死锁或超时,那么升级就不适用于您的环境(请参阅上面关于锁升级的讨论)。要考虑增加 LOCKMAX 或 LOCKS PER TABLE(SPACE)。另一种可能的解决方案就是使受影响的应用程序在初始化时发出 LOCK TABLE。

  另外,监控锁定/解锁请求、暂挂和声明/放弃请求的总数目。监视那些数量趋势(预期的或其他的)是在 DB2 UDB 环境中维护高水平的数据并发性的关键。

  报警极限

  特定页面或行被锁定的概率可能有较大变化,这取决于数据库设计、应用程序代码、事务量、可用的系统资源(例如 CPU、内存、I/O 等)和许多其他的因素。作为一般的经验法则,如果一页或一行被锁定的时间超过 10%,就认为它是“热的(hot)”。因此,10% 可视作合理的报警极限。少于 10% 通常视为是可以接受的,而大于 10% 则可能暗示竞争过多,并且可能应进行调查。

  在多数环境中,锁的平均持续时间少于 1 秒钟。如果您装置的锁平均持续时间大于 1 秒,可能就必须进一步进行分析了。



  数据共享考虑

  以下几段强调了运行 DB2 UDB 数据共享(Data Sharing)环境时,应该查看的信息。某些讨论仅仅与数据共享有关,而某些材料则可能适用于通常状况下的 DB2 UDB(甚至在前面可能已经提及),但对于数据共享环境中的考虑特别重要。

  顺序批插入计划

  任何按顺序执行 INSERT 的批处理应用程序都可能导致表空间的空间映射页面上的严重竞争。对于这样的应用程序,在创建用于它们的表空间时要考虑使用参数 MEMBER CLUSTER。通过该选项,DB2 UDB 在为 SQL INSERT 语句指派空间时,就不考虑群集索引。因此,DB2 UDB 将基于可用空间,将插入置于数据库中,这意味着不只是一次或几次访问不同的空间映射页面。这将极大地减少对于空间映射页面的竞争。

  选择性的分区锁定

  DB2 UDB V5 引入了选择性的分区锁定,这意味着仅仅锁定要访问的分区。这是通过在 CREATE 或 ALTER TABLESPACE 语句中指定 LOCKPART YES 选项来完成的。

  该功能主要使用在数据共享的环境中,允许 DB2 在分区级别上支持显式的层次结构锁定。选择性的分区锁定为 L 锁提供了真正的分区独立性,因而当数据共享组中的每个成员处理不同的分区时,避免了 IDRWI(DB2 内部读/写兴趣)。在 DB2 UDB V5 之前,表空间的父 L 锁定是在表空间级别上的,甚至对于分区的表空间也是如此。

  锁避免

  在 DB2 数据共享组中,建议进行下列实践,用以最大化锁避免,并因此提高并发性:

  • 在运行于分组里任何成员中的所有应用程序进程中进行频繁提交。
  • 停止(Quiesce)分组中非活动的成员。
  • 快速地重新启动失败的成员,以便解决处于质疑中的事务。
  • 在可能时对计划和包使用 CURRENTDATA(NO)。

  LOCKSIZE

  对于数据共享系统,我们极力建议使用 LOCKSIZE PAGE。在使用 LOCKSIZE ROW 之前,用户应仔细分析数据库设计和将访问该数据的应用程序进程的本质。数据共享的 DB2 UDB 系统由于页面物理锁(P-lock)竞争和协商,招致了行锁定的附加开销。通过较高的事务速率,应用程序在页面 P 锁之后,趋向于串行化。因此,要按照设计默认值使用页面锁定。

  减少竞争

  DB2 数据共享环境中有 3 种不同类型的竞争:

  1.   假(False)竞争- 单个 DB2 UDB 成员计算一个哈希(hash)值,用于确定将资源指派给哪个锁表条目。锁表存储在耦合设备中。此哈希值是通过使用资源名称,以及惟一地定义将要锁定资源的相关信息来计算的。虽然特定的资源总是产生一个(且只有一个)值来确定它在锁表中的位置,但极其可能有不止一个资源将散列到相同的锁表条目,该状况称作假(false)竞争。

  2.   XES 竞争- 耦合设备中的锁管理器和 XES 只承认两类锁模式 - 共享(S)模式和排他(X)模式。但是,IRLM 支持许多其他的模式,例如意图共享(intent-share,IS)、意图排他(intent-exclusive,IX)和意图排他的共享(share with intent-exclusive,SIX)。该情况导致从 ERLM 传递到 XES 时,将缩减锁模式。如果 XES 接着试图注册一个与现有锁不兼容的锁,XES 就必须检查 IRLM,以便更精确地确定所需的锁类型,以及这一锁请求是否真的兼容。如果该调查确定那些锁实际上是兼容的,那么就仅仅为 XES 竞争。如果那些锁实际不兼容,那么该竞争就是真的。

  3.   真(Real)竞争- 这是一种“传统的”竞争,通常由两个应用程序之间 IRLM 不兼容性导致,这在非数据共享的环境也会发生。

  通常建议的经验法则显示所有锁请求会导致竞争(真的或假的)应低于 2%。应对导致多于 2% 竞争的应用程序进程进行进一步的调查,用以确定这些竞争是真的还是假的。

  锁结构的大小可以影响数据共享环境中遭受的 false 竞争的数量。如果有更多锁表条目,那么两个不同资源“散列”到同一条目的机会就更少。因此,通常可以通过增加耦合设备中锁结构的大小来减少假竞争。如果所有竞争中超过一半为假竞争,我们就建议您扩展锁结构。

  如果大多数竞争是“真的”,那么应评估所熟知的数据库和应用程序设计原则,看是否适用于该情况。下列动作通常有助于减少实际竞争:

  • 对表访问和更新使用一致次序。
  • 优化事务以减少整个经过时间。
  • 频繁地增加 PCTFREE 和运行 REORG 实用程序。
  • 将跟新推迟到就正提交之前进行。
  • 评估是否使用 LOCKSIZE ROW。
0
相关文章