中国IT动力,最新最全的IT技术教程
最新100篇 | 推荐100篇 | 专题100篇 | 排行榜 | 搜索 | 在线API文档
首 页 | 程序开发 | 操作系统 | 软件应用 | 图形图象 | 网络应用 | 精文荟萃 | 教育认证 | 硬件维护 | 未整理篇 | 站长教程
ASP JS PHP工程 ASP.NET 网站建设 UML J2EESUN .NET VC VB VFP 网络维护 数据库 DB2 SQL2000 Oracle Mysql
服务器 Win2000 Office C DreamWeaver FireWorks Flash PhotoShop 上网宝典 CorelDraw 协议大全 网络安全 微软认证
硬件维护  CPU  主板  硬盘  内存  显卡  显示器  键盘鼠标  声卡音箱  打印机  机箱电源  BIOS  网卡  C#  Java  Delphi  vs.net2005
  当前位置:> IBM专区 > DB2 > 性能
仅供程序员们参考: 提交的乐趣
作者:佚名 时间:2005-08-10 17:02 出处:互连网 责编:小渔
              摘要:仅供程序员们参考: 提交的乐趣


2003 年 1 月
及时进行提交会收到良好的效果。

简介
几年前,我写过题为 “The Woes of Commitment”的专栏文章( DB2 Magazine,1998 年秋),在此文中讨论了 COMMIT 对在 DB2 for OS/390 下运行的应用程序资源的影响。我强调了在 COMMIT 时可能有什么损失,以及如何尽可能减少或甚至避免发生此类损失。在本专栏文章中,我将说明通过 COMMIT 能获得什么。在下一篇专栏文章中,我将介绍一些关于在多长时间间隔内进行 COMMIT 的想法。

所有批处理作业都应该有 COMMIT 逻辑。预先编码 COMMIT 逻辑比将该逻辑修改并添加到现有产品程序中更容易。因此,在这个问题上,我将讨论为什么在所有程序中都应该进行 COMMIT 。我将首先讨论 COMMIT 的主要功能,然后讨论如果不进行足够的 COMMIT ,那么 DB2 怎样提供保护、使我们免受麻烦、或对我们不进行足够的 COMMIT 进行“惩罚”。

为什么要 COMMIT ?
程序需要拥有锁、声明、工作单元作用域信息、日志缓冲区空间以及其它资源。 COMMIT 帮助释放这些资源,并减少 DB2 子系统上的一些约束。

COMMIT 释放锁。如果不允许只为一人之需而锁定整个表,那么就需要对数据的访问进行序列化。为了做到这一点,通常对表空间使用页面级的锁定。在使用页面级的锁定进行更新时,必须首先获取表空间级的锁,然后获取表或分区锁(假定 LOCKPART YES )。只有在获取了这些较高级别的锁之后,才能获取页面锁。当对多个页面持续进行更新、插入和删除操作,则需要一个接一个地获得更多的页面锁。获取每个页面锁需要一次跨内存服务调用,并且需要大约 250 字节的内存(用于跟踪)。如果随机地对 1000 行进行更新,则需要锁定多达 1000 个页面,这样大约会消耗 250000 字节的内存(包括表空间和表/分区锁)。

当不停地在多个页面上进行操作时,就要不断地获取页面锁。假定设置了 ISOLATION CS ,则当 COMMIT 时,会释放页面锁。 COMMIT 引起跨内存服务调用,以释放所持有的页面锁或行锁。如果让程序执行 RELEASE COMMIT ,那么同时也会释放表空间锁以及表或分区锁(请参阅 Programmers Only, DB2 Magazine,1998 年秋)。这样, COMMIT 释放了锁资源。

因为定期释放页面锁是如此重要,也因为无法强制程序执行 COMMIT 逻辑,还因为锁会消耗资源,所以有很多方法用来控制(从程序外部)任一用户所允许持有的锁的数目。

捕获未能充分 COMMIT 的程序。系统级参数( NUMLKTS )控制任一用户在任何一个表空间上可以持有锁的最大数目(行锁、页面锁、表锁、分区锁和表空间锁的总和)。假设程序在页面这一级锁定表。当已经达到系统所允许锁数目的最大值(假定为 5000),但违例线程还在请求表空间上的另一个页面锁时,DB2 将尝试通过将锁定从页面级别提升到表级别或分区级别,以缓解锁定“拥塞”。通过采用更高一级的锁定,DB2 可以释放所有低级的页面锁。为了成功地进行锁定升级,锁管理器必须能够获得更高一级(表或分区)的保护性锁。

例如,如果线程获得了表空间上的一个 IX 锁、表上的一个 IX 锁以及该表页面上的 4998 个 X 锁,然后当请求另一个锁时,DB2 将尝试通过获得该表上的 X 锁(用 X 锁来替代或升级 IX 锁)来提升锁定级别。如果锁定升级成功,则会释放该页面上所有的 X 锁。将由两个锁(一个是表空间上的锁,另一个是表上的锁)替代 5001 个锁。代价是什么呢?当然,并发性降低了,并且,如果这时有用户正在并发地访问该数据,则很可能会失败。当 DB2 不能升级锁定时,提出请求的线程会接收到 -911 SQLCODE ,因为它等待表或分区上的 X 锁超时了。当该用户可以升级锁定时,所有其它用户会接收到 -911 SQLCODE 。两种情况都不好。

特定于表空间的参数 MAXLOCKSDDL CREATEALTER 表空间语法的一部分。使用这个参数,您可以通过将特定表空间的升级值设置为更高的限制、更低的限制或根本无限制,以覆盖由 DSNZPARM 建立的系统级限制。

另一个 DSNZPARMNUMLKUS )可以用来限制任一用户在任一时间可以持有的所有资源上的页面锁和行锁的总数。当达到这个“每用户锁的最大数目”,并且违例用户尝试再获取一个锁时,该用户就会接收到 -904 ,该 SQLCODE 表示无法获得资源(锁)。

对任一作业可以持有的锁数目进行管理的最后一种方法是使用 BIND 功能。例如,假定您在运行 SPUFI 时不经意地将 SPUFI 缺省面板设置为 ISOLATION RR ,并且优化器在动态 BIND 期间选择将表空间扫描用于存取路径。优化器知道,在查询完成时,它将请求并持有每个页面或行上的锁。因此,优化器在 BIND 时将 LOCKSIZE 更改成 TABLE (或 PARTITION )。 BIND 时的自动锁升级是系统(在您的程序逻辑之外)使用的保护性的管理机制,用来控制任一用户可以获取的锁的数目。

避免锁
也有首先避免获取锁的方法。在数据定义语言(Data Definition Language,DDL) CREATE 语句中,应该选择最低的锁定级别,这是多个用户并发地访问表时所需要的。您应该知道, LOCKSIZE 越高(越大),所需要的锁就越少。因此,如果您有只读数据(或成批维护的无任何并发用户的数据),那么 LOCKSIZE TABLESPACETABLE 是合适的,并且很理想。但是,如果需要更细粒度的锁定级别,以便有效地将多个并发用户对表的访问进行序列化,那么就选择更细、更低的级别 LOCKSIZE PAGE 。然后,在每个程序中,都要确定:是否可以通过显式地请求从 PAGETABLEPARTITION 级别锁定的 LOCKSIZE 升级,以安全地覆盖该特定程序的页面级别锁定。

当对您的程序编码时,如果程序是执行维护的独立程序,并且您希望防止任何并发的阅读器和写入器访问作业,可以通过使用以下 SQL 语句( LOCK TABLE tablename IN EXCLUSIVE MODE )明确地请求升级 LOCKSIZE 。如果您的程序是只读的,并且与其它只读作业并发运行,则可以使用以下 SQL 语句: LOCK TABLE tablename IN SHARE MODE

如果表空间已分区并且将 LOCKPART 参数设置成了 YES ,那么也可以指定分区号。如果从不在分区级别使用锁定表语句,并且在锁升级方面从来没有发生过问题,那么应该将 LOCKPART DDL 参数设置成 LOCKPART NO 。当使用 LOCKPART YES 并指定 LOCKSIZE PAGE 时,必须首先获得表空间锁,随后获取分区锁,然后获取页面锁。当指定 LOCKPART NO 时,您必须首先获取表空间锁,然后获取页面锁。换句话说,使用 LOCKPART NO ,少获取一个锁,同时也少释放一个锁。那么,如果您从不需要或使用分区级别的锁,为什么要将 LOCKPART 设置成 YES 呢?

另一种锁避免技术涉及临时表的使用。当从临时表进行插入、更新或删除时,您并未获取临时表上的锁。可以将临时表用作保存区域,用来容纳计划插入到实际 DB2 基本表的所有行。如果该工作单元不成功并回滚,那么您不会持有基本表上的锁,因此也不存在针对基本表的回滚开销。如果一切顺利并且通过从临时表进行选择来 INSERT 到基本表,然后再 COMMIT ,那么,持有锁的时间就相对短了。

BIND 程序时,为 BIND 参数 ISOLATION 选择最佳值是另一种限制必须获得的锁的数目的方法。这个参数告诉 DB2 如何为只读 SQL 处理页面和行级别的锁。该参数的缺省值是 RR,但这个值几乎总是被覆盖。提供最小的锁定开销的值是 UR 。但是, UR 也对“脏”数据提供了最少的保护。 UR 允许阅读器从运行中的工作单元(换句话说,就是未提交的数据)访问数据。如果您知道没有未提交的数据或者对是否读未提交的数据并不在意,那么可以用 ISOLATION UR 来进行 BIND ,或在 SQL 语句中使用 WITH UR 子句。

但是,如果您需要只访问“干净的”、已提交的数据,那么就必须用其它 ISOLATION 值进行 BIND 。值 CS 只需要最小的开销,并确保只读取来自已提交工作单元的数据。当选择了 CS 时,另一个 BIND 参数 CURRENTDATA 与之相关。这个参数告诉 DB2 在读干净的、已提交的、限定的表行时您希望做什么。您希望不锁定页面就获得该行的“干净”映象,还是宁可为获得该“干净”映象而付出锁定页面的代价呢?您可以自行选择:不带锁的“干净”行( CURRENTDATA NO )或是带锁的“干净”行( CURRENTDATA YES )。当然,前者所需的开销要小得多。但是为了让 ISOLATION CSCURRENT DATA NO 正常地工作,其它程序必须不断发出 COMMIT

当设计应用程序时。最后要考虑的问题是使用实用程序,如 ONLINE LOADREORG DISCARD 。实用程序使用 CLAIM/DRAIN 逻辑而不是锁定来控制并发性。通常,您还可以带 LOG(NO) 运行实用程序以避免记录日志的开销。

您会表示反对:“然而我不必提交,因为没有获取任何我希望释放的锁。我正在锁定整个表空间或正在以 ISOLATION UR 的形式使用锁避免的技术。”

好,您已经为避免获取任何不必要的锁而做了您所能做的一切。但还是需要经常进行 COMMIT 。是的,您确实要这样做。但除了锁定问题,还有其它引起 COMMIT 的原因。

提交释放声明
在 V3 中,DB2 开始使用 Claim Manager(Buffer Manager 的组件)来序列化 SQL 和实用程序对某些对象的访问,这些对象包括表空间或表空间分区,以及索引空间或索引空间分区,实用程序在这些对象之上运行。每当程序第一次使用表空间、表空间分区、索引空间或索引空间分区时,程序获取该对象上的 CLAIM 。当 SQL 和实用程序出现冲突时(例如,程序在对象上有声明,而实用程序想接管该对象),实用程序在对象上放置一个 DRAINDRAIN 禁止更多的声明,并允许现有的声明消失。实用程序等待建立 CLAIM 的程序消失(它等待的时间量取决于 DSNZPARM ),实用程序要么超时,要么成功地进行 DRAIN 。为了让 DRAIN 成功,在实用程序放弃超时期间内,必须释放所有冲突的 CLAIM 。那么如何释放它们呢?由程序发出隐式的或显式的 COMMIT

CLAIM 不是由 BIND RELEASE 参数控制的。您可以使用 RELEASE DEALLOCATE 进行 BIND ,但在 COMMIT 时仍会释放 CLAIM 。仅当对象上有打开的 CURSOR WITH HOLD 时, COMMIT 时才不会释放该对象上的 CLAIM

DB2 的最新发行版的特点是联机实用程序,它同访问同一表空间或分区的程序并发运行。但是联机实用程序的成功或失败,取决于该实用程序在短时间内用 DRAIN 放弃在对象上进行 CLAIM (有时是 WRITE CLAIM ,有时是 READWRITE CLAIM 两者)的所有程序的能力。这意味着联机实用程序的成功或失败取决于正在并发运行的程序,那些正在进行维护和只是进行读操作、在 DSNZPARM 指定的放弃等待时间内发出 COMMIT 的程序。

一些 SQL 到 SQL 兼容性也使用 CLAIM/DRAIN 逻辑。V7 引入了更新正在分区的索引中值的能力,因此不必使用 DELETEINSERT 程序逻辑,即可将行从一个分区移动到另一个分区。但是,要使更新成功,正在更新的程序必须 DRAIN 受到影响的分区和任何相关分区上的所有 CLAIM 。要使分区键更新 DRAIN 成功,正在访问分区的其它程序必须进行提交并释放其 CLAIM

您会说:“没有实用程序与我的程序并发运行,没有人尝试更新分区键,并且我对页面锁使用完全锁避免技术。我为什么要提交呢?”

提交清空日志缓冲区空间
当执行插入、更新和删除时,那么您就创建了 UNDOREDO 日志记录。仅在程序发出显式或隐式 ROLLBACK 的情况下,才创建 UNDO 记录。 REDO 记录是为 RECOVER 实用程序创建的。它们可以应用于 IMAGE COPY ,以将数据回到恢复时的副本状态。日志记录被写到数据库服务地址空间(Database Services Address Space(称为 DBM1))中的日志缓冲区。在 COMMIT 时,您告诉 DB2 您再也不需要用于 ROLLBACK 的日志记录了。可以从内存中删除它们。因此,DBM1 中的 DB2 Data Manager 对系统服务地址空间(Systems Services Address Space)进行跨内存服务调用,以将日志缓冲区刷新成活动的日志数据集。

如果您在正在进行大量维护的程序中 COMMIT 失败,那么 DB2 将自行清除日志缓冲区。有 DSNZPARM 用来通知 DB2 隔多长时间 CHECKPOINT 系统和刷新日志缓冲区。可以两种单位指定这个值,每隔多少次更新进行刷新或每隔多少秒进行刷新(后者从 V7 开始)。即使您的程序没有发出 COMMIT ,日志缓冲区也会被清空。那么您需要注意些什么呢?转到工作单元,您将日志记录写到了内存中。如果必须回滚( ROLLBACK ),则将从磁盘读 UNDO 日志记录。如果您已经向活动日志数据集写了足够多的日志记录,将它填满,那么 ROLLBACK 可能会读压缩的磁带。这是程序可能运行两小时而 ROLLBACK 要四小时的原因之一。

您会说:“我不锁定页面,我不并发地运行实用程序,我在作业主要进行读并很少有更新。我为什么要 COMMIT 呢?”

提交使工作单元变得坚固
今天您的作业只进行少量维护,但明天未必也是如此。公司(如果一切良好的话)会成长和收购其它公司。主动地编码 COMMIT 逻辑比被动编码轻松得多。如果程序没有显式 COMMIT ,则工作单元就是程序的全部。当程序发出成功的 STOP RUN 时,这个巨大的工作单元隐式地进行了 COMMIT 。锁、声明和日志记录由线程拥有,直到程序完成为止。如果程序异常终止,则回滚处理程序的整个作用域。当然,这意味着程序已经完成的任何工作都会丢失。没有从程序离开处进行恢复的 RESTART 。只有进行 RERUN ,重复所有已经完成但丢失的工作。

长期运行的工作单元通常会碰上一个或多个先前提到的系统管理的 DSNZPARMS ,并会对其它用户和实用程序造成混乱。

工作单元也可能包含未提交的自动重新联编(autorebind)。如果程序 A 调用程序 B,而 B 的包无效(也许 DBA 删除或重新创建了该包使用的索引),那么当 B 执行时,它必须自动重新联编。自动重新联编是程序 B 的工作单元作用域的一部分。因此,如果 B 回滚,则自动重新联编也回滚,引起 DB2 Catalog 锁定问题。锁定 DB2 Catalog 并不是好事情。

从我在本文中和先前的专栏文章中列出的理由来看, COMMIT 是如此的重要。您甚至可以用 DSNZPARM 来向操作员报告任何运行了指定数目的检查点间隔而未发出 COMMIT 的程序。这种“饶舌”的功能使您可以标识那些不合规矩的程序,它们很少或根本不进行 COMMIT

几句结束语
当您询问关于如何设计访问 DB2 的程序时,可能会意识到答案取决于许多不同因素。没有简单的事情。Roger Miller(IBM 的首席 DB2 for z/OS 战略分析师)喜欢说:“DB2 中没有金科玉律。”有的只是关于 BIND 参数、 DSNZPARM 、DDL 参数、程序逻辑等的决策。

这样做的优点是,这些决策派生的所有可能的组合之间存在细微的差别,尝试理解这些差异的人将获得作业的安全性。缺点是决策并不容易。因此,当您决定是否以及按何种时间间隔进行 COMMIT 时,请考虑本专栏文章和下一篇文章以及 参考资料中所列的先前的专栏文章中的信息。

关闭本页
 
首页 | 投资与合作 | 服务条款 | 隐私政策 | 收藏本站 | 设为首页 | 新用户注册 | 免责声明 | 使用帮助
Copyright ©2005-2008 chinaitpower.com All rights reserved. www.chinaitpower.com 版权所有