第9章:TUXEDO的数据库编程_MQ, Tuxedo及OLTP讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  MQ, Tuxedo及OLTP讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 5128 | 回复: 0   主题: 第9章:TUXEDO的数据库编程        上一篇   下一篇 
yd7563337
注册用户
等级:少将
经验:10191
发帖:128
精华:1
注册:2011-8-9
状态:离线
发送短消息息给yd7563337 加好友    发送短消息息给yd7563337 发消息
发表于: IP:您无权察看 2014-9-16 10:45:16 | [全部帖] [楼主帖] 楼主

9.1 TUXEDO 如何处理分布式事务 在两层的 C/S 结构中,客户端直接访问数据库,当采用 TUXEDO 中间件后,形成三层结构。 这时,客户端不直接访问数据库,而是改为调用中间件 TUXEDO 服务端上的服务,由 TUXEDO 服务端访问数据库,并把结果返回给客户端。如图所示。
客户端

客户端

TUXEDO 应 用服务器

数据库

客户端

TUXEDO 支持 ORACLE、SYBASE、INGRES、DB2 等 UNIX 上的大型数据库和 NT 上的 SQL Server,并且还支持 C-ISAM 文件系统。即可以通过 XA 协议, 也可不用 XA 协议与数 据库进行连接。 在说明 TUXEDO 与数据库的连接的配置及编程之前, 我们先解释一些名词。 名词解释 资源管理器(RESOURCE MANAGER): 资源管理器 : 最常见的是数据库,可以是其他的,如 TUXEDO 的 QUEUE,EJB 的 JMS 等,它们对数据进 行管理和维护. 事务(TRANSACTION): 事务 事务的定义很多,简单地说,事务是对资源管理器的一组操作,它使所涉及的资源管理器从 一个状态转变到另一个状态,这些操作要么全部成功,要么全部失败。事务具有以下的 4 个特征(一般称为 ACID): 原子性(ATOMICITY): 指事务中的所有操作作为一个整体单元要么成功要么失败. 原子性 一致性(CONSISTENCY):一致性意味着不管事务提交或放弃,参与事务的所以资源管理器 一致性 在事务结束后都保持一种合法的状态.一致性也意味着,当一个事务结束时,所有的参 与者都要释放它所锁住的资源. 隔离性(ISOLATION):隔离性意味着事务正在处理过程中,在事务外面无法看到事务处理的 隔离性 中间结果. 持久性(DURABILITY):使事务的最终结果已被真正写到磁盘系统中. 持久性 本地事务(LOCAL TRANSACTION): 本地事务 : 如果一个事务只涉及到一个资源管理器,那么该事务称为本地事务。在 TUXEDO 中,不通过

XA 接口的事务都是本地事务,如:在 ORACLE 中,用 EXEC CONNECT 建立与数据库的连接, 并用 EXEC COMMIT 提交一个事务,那么该事务就是本地事务. 全局事务(GLOBAL TRANSACTION): 全局事务 : 全 局 事 务 涉 及 到 一 个 或 多 个 资 源 管 理 器 , 它 也 称 为 分 布 式 事 务 (DISTRBUTED TRANSACTION),对所有涉及的资源管理器的操作必须被看作单个工作单元。它们必须被 同步,并在所有服务器上圆满完成,否则,就必须被彻底取消。例如:一个服务器在写过程 中被关闭,那么事务处理中其他系统上的所有写的东西就必须被取消。在 TUXEDO 中,采 用 XA 接口的事务都是全局事务。全局事务是相对 LT 而言的,它也有 AICD 四个特性,所 不同的是它可以跨越多个资源管理器,这些资源管理器可能在不同的平台上。在 TUXEDO 中,一个全局事务最多可跨越 16 个不同的资源管理器. 事务管理器(TRANSACTIN MORNITOR): 事务管理器 : 管理协调参与全局事务的各个资源管理器的准备, 提交及回滚等操作, 事务管理器还在出现 场地故障、网络故障或全局资源死锁时协调全局事务的恢复。TUXEDO 在全局事务中就充 当事务管理器的作用。在一个全局事务中有一个事务协调器,有一个以上的资源管理器。事 务协调器与资源管理器之间采用 XA 协议进行通讯在 TUXEDO 中一个 GROUP 只能有以个 资源管理器,所以一个全局事务会跨越多个 GROUP XA 协议 协议: XA 协议由 TUXEDO 首先提出,并交给 X/Open 组织, 作为资源管理器(数据库)与事务管理器 的接口标准。Informix 是最早宣布支持 XA 协议的数据库厂家,Informix5.0 以上的版本都提 供 XA 接口,以实现与 TUXEDO 的连接。目前,Oracle、Informix、DB2、Sybase 等各大数 据库厂家都提供对 XA 的支持.XA 协议采用两阶段提交方式来管理分布式事务.XA 接口提供 资源管理器与事务管理器之间的进行通讯的标准接口,TUXEDO 支持基本的 XA 规范 (PRELIMINARY XA SPECIFICATION),及最终的 XA 规范(THE FINAL SPECIFICATION).XA 协议包括两套函数,以 xa_开头的及以 ax_开头的. 以下的函数使事务管理器可对 RM 进行操作 xa_open, xa_close:建立,关闭与 RM 的连接 xa_start,xa_end:开始,结束一个本地事务 xa_prepare,xa_commit,xa_rollback:预提交,提交,回滚一个本地事务 xa_recover:回滚一个已进行预提交的事务 ax_开头的函数使 RM 可以动态在事务管理器中进行注册,并可以对 XID(TRANSACTION IDS)进行操作. 说明: 在 FINAL XA SPECIFICATION 中,用 XID 代替全局事务 RID ax_reg,ax_unreg:允许一个 RM 在一个 TMS(TRANSACTOIN MANAGER SERVER)中动态注 册或撤消注册. 全局事务 rid_开头的函数在 PRELIMINARY XA SPECIFICATION 中有,在 FINAL XA SPECIFICATION 中没有定义. 全局事务 rid_cmp:比较两个全局事务 RID 全局事务 rid_fmt:格式化一个全局事务 RID,以便打印
 全局事务 rid_hash:根据全局事务 RID,生成一个 HASH 值. 现在主要的数据库都支持 FINAL XA SPECIFICATION

TUXEDO 中的全局事务有以下特点: 中的全局事务有以下特点: 1. 可以在客户端或服务端开始一个全局事务 2. 在 TUXEDO 中全局事务能跨越多个进程 3. 每个全局事务有一个唯一的 ID 号(全局事务 RID)标识,它可在 TUXEDO 的进程间传 递。 4. 全局事务可以跨越 DOMAIN

一个全局事务从发起到提交的过程: 一个全局事务从发起到提交的过程 全局事务的提交采用两阶段提交方式. 在两阶段提交过程中,应用程序是事务提交得发起者,应用程序通过调用 TPCOMMIT()开始 一个事务的提交,该应用所在 GROUP 的 TMS 是这个事务的协调者(COORDINATOR),我们称 之 为 COORDINATOR TMS, 其 他 参 与 本 事 务 的 GROUP 所 对 应 的 TMS, 我 们 称 之 为:SUBORDINATE TMS,CORDINATOR TMS 负责与所有参与本全局事务的 RM 的通讯.完 成该事务的提交,整个过程如下: 1. 应用程序通过调用 TPCOMMIT()开始一个事务的提交 2. CORDINATOR TMS 给参与该全局事务的每个 GROUP 中的 TMS 发送请求,每个 TMS 要 求 RM 进行预提交操作 3. 每个 RM 各自进行预提交,预提交指把要更改的数据写到磁盘上,以便在失败时可以进行 恢复.但没有真正更新 RM 4. 当 RM 预提交完毕,每个 SUBORDINATE TMS 都把预提交操作结果(成功或失败)告诉给 COORDINATOR TMS, 5. 如果有任何一个 RM 预提交失败,或 COORDINATOR TMS 得不到它的应答,那么 COORDINATOR TMS 告诉所有的 RM 回滚它们各自的本地事务. 如果所有的 RM 预提交都成功,那么 COORDINATOR TMS 在它所在机器的 TLOG 文件中 写一条记录,内容包括该全局事务的全局事务 RID,参与该全局事务的所以 RM 的列表及 其他信息.以便在第二阶段提交失败时回滚. 6.下一步做什么取决于在 UBBCONFIG 中 TP_COMMIT_CONTROL 得设置 TP_CMT_LOGGED TPCOMMIT()调用返回,程序继续往下走,各个 RM 各自进行真正得提交操作,如果在第二阶段 提交失败,TMS 时是不知道的.而这是 TPCOMMIT()已返回成功. TP_CMT_COMPLETE TPCOMMIT()会等到第二阶段提交完成才返回,如果在第二阶段提交失败,TMS 是知道的. 并且 TPCOMMIT()会返回失败. 默认值是 TP_CMT_COMPLETE

事务模式和非事务模式: 事务模式和非事务模式: TUXEDO 的应用程序可分为两种,事务模式和非事务模式,有以下两种方式可以使一个 TUXEDO 应用处于事务模式下: 1.显式: 通过调用 tpbegin(),显式开始一个全局事务 2.隐式: 在 UBBCONFIG 中对一个 SERVICE 设置了 AUTOTRAN=Y,当该 SERVICE 被调用 时,TUXEDO 会自动启动一个全局事务 注意:AUTOTRAN=Y 只对全局事务起作用,对本地事务不起作用。 例如:下面的程序中 SERVICE A 中调用了 SERVICE B A(TPSRCINFO *RQST) { tpcall(B,..........,flags) } 那么: 如果 A 当前不处于事务模式中,A 在 UBBCONFIG 中设置了 AUTOTRAN=Y,则系统 自动起一个全局事务 如果 A 当前已处于事务模式中,A 在 UBBCONFIG 中设置了 AUTOTRAN=Y,则: 1. 如果 tpcall()中的 flags 没有设置 TPNOTRAN(默认方式是不设置)那么 SERVICE:B 参与当前的事务。 2. 如果 tpcall()中的 flags 为 TPNOTRAN,那么 SERVICE:B 不参与当前的事务。 注意: 如果在 UBBCONFIG 中对 SERVICE B 设置了 AUTOTRAN=Y,那么当前的事务被 自动挂起,系统为 B 自动起一个新的事务,如果 B 中的事务失败了,对 A 中的事务没有 影响。

TUXEDO 中与全局事务有关的设置 中与全局事务有关的设置: 与全局事务有关的设置包括:RESOURCE,MACHINE,GROUPS,SERVICE 4 个节 中设置主要有: 在 RESOURCE 中设置主要有 MAXGTT(MAX Global Transaction) ( ) 在任一时刻在某一台服务器上最多可以有多少个全局事务存在,也就是可以最多有多少 个未提交的全局事务,范围:0-32767。0 意味着该系统不支持事务,默认值是 100.该值也可在 MACHIENS 中设置。在 MACHINES 中设置的值回覆盖在 RESOURCE 中的设置值。 GTT(Global Transaction Table) TUXEDO 在 BULLETIN BOARD 中维护的一张表,用于记录全局事务的状态信息,在该 台服务器上发起的或该台服务器参与的每个全局事务,在 GTT 中都对应一条记录.每台服务 器的 GTT 中最多可以有多少条全局事务的记录是由 MAXGTT 决定的,当一个全局事务成功 提交时,它在 GTT 中的记录将被删除.如果该全局事务提交失败,它在 GTT 中的记录还会保持 一段时间,一般是 5 分钟.所以 GTT 应该大于 TLOGSIZE。GTT 的范围为 0-64,000,默认值为 100,可以在 MACHINE 中覆盖该设置.

CMTRET: 设置 TP_COMMIT_CONTROL 的初始值,该值可以由在 tpscmt()中重新设置.

有关的设置: 与 MACHINES 有关的设置 在 MACHINES 中设置每台机器中的全局事务 日志文件的位置和大小. TLOGDEVICE: 指定包含 TLOG 文件的 TUXEDO 文件系统名,长度不能超个 64 个字符 TLOGNAME: TLOG 文件的名称 TLOGSIZE: TLOG 文件的大小,它的大小为 0 <= TLOGSIZE <= 2048,默认值为 100 如果一个全局事务跨越多台服务器,在每台服务器上都应该有一个 TLOG 文件,用于记录全局 事务的信息,在全局事务要回滚时要用到记录在 TLOG 中的信息.TLOG 可以创建在裸设备上. 如果一个全局事务在该台机器上发起但还没有提交,那么它在 TLOG 文件种占有一页的空间, 在一般的平台上一页的大小使 512 字节,,当该全局事务成功提交或回滚后,它在 TLOG 中的记 录将被删除。 有关的设置: 与 GROUPS 有关的设置 在 TUXEDO 中一个 GROUP 中只能定义一个资源管理器,如果一个 TUXEDO 系统有多个资 源管理器,就要定义多个 GROUP 才行.在 GROUP 中定义 TMS 的名称及个数,打开,关闭该资 源管理器的参数,不同的资源管理器的设置都不一样.

SERVICES 中的设置 中的设置: AUTOTRAN: 如果一个客户端调用了设置 AUTOTRAN=Y 的 SERVICE,并且该客户端当前不在事务模式下, 那么 TUXEDO 自动为该 SERVICE 启动一个全局事务. TRANSTIME: 对因设置了 AUTOTRAN 的而起动的事务设置超时时间.默认为 30 秒,0 表示没有超时。

全局事务中用到的函数 在 TUXEDO 中有两套操作全局事务的函数 ATMI TUXEDO 自己提供的函数 TX 由 X/OPEN 定义的函数 它们基本差不多,因为 TX 是以 ATMI 为基础定义的.区别 1. TX 提供 CHAINED TRANSACTIONS,ATMI 不提供 2. TX 以 tx_开始,而 ATMI 以 tp 开始,如 tx_begin(),tpbegin() 3. TX 在 tx.h 中定义,ATMI 在 atmi.h 中定义

TX_开头的函数一般很少用,在此不作介绍。

int tpopen(void) 描述: 建立与一个资源管理器得连接,该资源管理器是该 SERVER 所在得组中定义的.并且在 buildserver 时要用 -r 参数指定该资源管理器.如果采用默认的 TPSVRINIT()该函数会自动被 调用。 参数:无 返回值:失败返回-1, 错误号保存在全局变量 tperrno 中。

int tpclose(void) 描述:关闭与一个资源管理器得连接,如果采用默认的 TPDONE()该函数会自动被调用, 参数:无 返回值:失败返回-1,错误号保存在全局变量 tperrno 中。

int tpbegin(unsigned long timeout, long flags) 描述:开始一个全局事务 参数: timeout:全局事务的超时时间,从 tpbegin()开始,过了 TIMEOUT 秒该全局事务还没有完成, 该全局事务将因超时而回滚,范围为 0 to 2 31 -1 秒, 0 表示可以为无穷长。注 意:TIMEOUT 应该大于 SCANUNIT FLAGS:保留值,现在没有用到,要设为 0 返回值:失败返回-1,错误号保存在全局变量 tperrno 中。

int tpcommit(long flags) 描述:对一个全局事务进行提交操作 参数:FLAG 应该设为 0 返回值:失败返回-1,错误号保存在全局变量 tperrno 中。

int tpabort(long flags) 描述:对当前的全局事务进行回滚 参数:FLAG 应该设为 0 返回值:失败返回-1,错误号保存在全局变量 tperrno 中。

int tpsuspend(TPTRANID *tranid, long flags) 描述:挂起当前的全局事务,一个全局事务的发起进程 A 可以调用它,并把得到的句柄传给在 同一台机器上另一个进程 B,在 B 中可以调研 TPRESUME()重起该全局事务.如果不是在一个 全局事务的发起进程中调用 TPSUSPEND(),那么只能在进程中调用 TPRESUME().

注意:一个全局事务被挂起之后,超时仍然起作用. 参数: tranid:如果调用成功,返回一个当前全局事务的句柄 FLAG 应该设为 0 返回值:失败返回-1, 错误号保存在全局变量 tperrno 中。 成功返回一个当前全局事务的句柄,

int tpresume(TPTRANID *tranid, long flags) 描述:重新开始一个全局事务 参数: tranid:要重新开始的全局事务的句柄,为 tpsuspend()中参数 tranid 返回值 flags: 应该设为 0 返回值:失败返回-1,错误号保存在全局变量 tperrno 中。

int tpscmt(long flags) () 描述:设置 TP_COMMIT_CONTROL 得值 参数:FLAG 有两个值 TP_CMT_LOGGED: TPCOMMIT()在第一阶段完成之后就返回. TP_CMT_COMPLETE:TPCOMMIT()在第二阶段完成之后才返回. 返回值:失败返回-1,错误号保存在全局变量 tperrno 中。

int tpgetlev() 描述:返回当前所处得全局事务状态. 参数: 返回值:1:在一个全局事务中,0:不在一个全局事务中, 失败返回-1,错误号保存在全局变量 tperrno 中。

9.2 ORACLE 数据库 XA 的配置 TUXEDO 服务器可以和 ORACLE 数据库在同一台服务器上,也可以在不同的机器上,如果在不 同的机器上,在 TUXEDO 的服务器上安装一个 ORACLE 的客户端。从上一节的内容我们知道 TUXEDO 服务器与 ORACLE 数据库连接有两种方式: 1、 不通过 XA 接口直接互连。 适用于整个系统只有一个数据库的情况。 不需要作任何的配置。 2、通过 XA 接口互连,对整个系统有一个数据库或多个数据库都适用,要进行一些配置工作 才行。下面介绍采用这种互连方式的配置方法。 系统说明: 系统说明: TUXEDO 版本:7.1 安装目录 d:\tuxedo71 ORACLE 版本:8.1.5 安装目录 d:\ora81 操作系统: win2000 配置的步骤: 配置的步骤 一、ORACLE 的的配置 1.用 internal 用户(缺省的口令是 oracle)进入 SQLPLUS C:\>sqlplus internal/oracle 2.运行 ORACLE 的安装路径下的/rdbms/admin/xaview.sql SQL> @d:\ora81\rdbms\admin\xaview.sql 3.授权 SQL>grant select on v$xatrans$ to public with grant option; SQL>grant select on v$pending_xatrans$ to public with grant option; 4. 用 system 用户(缺省的口令是 manager)连接并授权 SQL>connect system/manager SQL>grant select any table to public; 二、TUXEDO 的配置 1. 修改 TUXEDO 安装路径的 udataobj 目录下的 RM 文件,把以 Oracle_XA:xaosw:开头 的一行用#注释掉,并加入一行: Oracle_XA;xaosw;d:\ora81\rdbms\xa\oraxa8.lib d:\ora81\precomp\lib\msvc\orasql8.lib 如果是在 UNIX 环境下,则为: Oracle_XA:xaosw:-L${ORACLE_HOME}/lib -lclntsh 2. 在 TUXEDO 用户下创建 TMS 文件:TMS_ORA8i,TUXEDO 通过 TMS_ORA8i 与 ORACLE 数据 库采用 XA 协议进行通讯 buildtms -o d:\tuxedo71\bin\TMS_ORA8i -r Oracle_XA 注意:如果 TUXEDO 服务端与 ORACLE 数据库不在同一台服务器上,可能会提示找不到 库文件 oraxa8.lib 和 orasql8.lib,可到 ORACLE 数据库的服务端相应目录下把这两

个文件拷到当前机器 ORACLE 的客户端下的对应目录下. 3. 配置 UBBCONFIG 1. 在*MACHINES 节中增加: TLOGDEVICE = "/home/oracle/temp/simpdb/TLOG" TLOGNAME=TLOG TLOGSIZE=200 2. 改*GROUPS 节的配置为:(scott/tiger 为本数据库所采用的用户及口令,可根 据需要更改) *GROUPS GROUP1 LMID=simple GRPNO=1 OPENINFO="Oracle_XA:Oracle_XA+Acc=P/scott/tiger+SesTm=600+MaxCur=5+ LogDir=." TMSNAME="TMS_ORA8i" TMSCOUNT=2 修改后的配置文件 ubb 内容如下 IPCKEY 123456 DOMAINID simpapp MASTER simple MAXACCESSERS 100 MAXSERVERS 50 MAXSERVICES 100 MODEL SHM LDBAL N *MACHINES server LMID=simple APPDIR="d:\test" TUXCONFIG="d:\test\tuxconfig" TUXDIR="d:\tux71" TLOGDEVICE = "d:\test\TLOG" TLOGNAME=TLOG TLOGSIZE=100 *GROUPS GROUP1 LMID=simple GRPNO=1 OPENINFO="Oracle_XA:Oracle_XA+Acc=P/scott/tiger+SqlNet=DEMO+SesTm=600+M axCur=5+LogDir=." TMSNAME="TMS_ORA8i" TMSCOUNT=2 *SERVERS DEFAULT: CLOPT="-A" test SRVGRP=GROUP1 SRVID=1 *SERVICES 说明: 说明:OPENINFO 的含义 P: 指定用户名与口令,通过该用户名与口令与数据库建立连接,上面的配置

中用户为 scott,口令为 tiger SqlNet: 如果 TUXEDO 服务器与数据库服务器不在同一台机器上,TUXEDO 上的 ORACLE 客户端通过网络方式与数据库服务器建立连接, 在 OPENINFO 中的 SqlNet 指定该数据库的 SID, 如在上面的配置中该数据库 的 SID=DEMO,如果不 SqlNet 指定,在生成的 TRC 文件中会有如下错误: xaolgn_help:XAER_RMERR:OCIServerAttach failer. ORA-12154 ORA-12154: TNS: 无法处理服务名 SesTm(Session time limit): maximum time a transaction can be inactive MaxCur: 最多可以同时打开多少个 CURSOR 4.重命名下列文件,因为下列文件名与 ORACLE 带的文件名有冲突,所以要改名。 1.TUXEDO 安装路径 include 目录下的下面文件 把 sqlca.h 改名为 sqlca.h.bbb 把 sqlcode.h 改名为 sqlcode.h.bbb 把 sqlda.h 改名为 sqlda.h.bbb 2.重命名 TUXEDO 安装路径 lib 目录下的下面文件 把 libsql.lib 改名为 libsql.lib.bbb 5.用 TMADMIN 创建 TLOG 文件,TUXEDO 用一个文件 TLOG 记录对数据库操作的日志。用于协 调分布式数据库的提交与回滚. D:\>tmadmin >crdl -b 500 -z d:\test\TLOG >crlog -m simple >q 三、服务端的程序:test.pc 服务端的程序: 功能:根据客户端传的 EMPNO 到表 EMP 中取 ENAME 的值,并把它返回给客户端 #include <stdio.h> #include <atmi.h> #include <userlog.h> EXEC SQL INCLUDE sqlca; EXEC SQL BEGIN DECLARE SECTION; long al_empno=0; char ac_ename[11]=""; EXEC SQL VAR ac_ename EXEC SQL END DECLARE SECTION; IS STRING(11);

TEST(TPSVCINFO *rqst) {
/*接收客户端来的数据*/ al_empno = (FBFR32 *)rqst->data; EXEC SQL select ename into :ac_ename from EMP where empno=:al_empno; if(sqlca.sqlcode!=0) { userlog("select from EMP failure,sqlcode=%ld, sqlerr=%s\n",sqlca.sqlcode,(char *)sqlca.sqlerrm.sqlerrmc); strcpy(rqst->data,sqlca.sqlerrm.sqlerrmc); tpreturn( TPFAIL, 0, rqst->data, 0, 0 ); } /*把取出的结果返回给客户端*/ strcpy(rqst->data,ac_ename); tpreturn( TPSUCCESS, 0, rqst->data, 0, 0 ); }


四、编写客户端程序: testcli.c 编写客户端程序: 功能:调用 TUXEDO 服务端的服务 TEST,取 EMPNO=1000 所对应的 ENAME 的值,并显示出来 #include <stdio.h> #include "atmi.h" main(argc, argv) { long reqlen=1024; char *reqbuf; /* 与 TUXEDO 服务端建立连接 */ if (tpinit((TPINIT *) NULL) == -1) { (void) fprintf(stderr, "Tpinit failed\n"); exit(1); } /* 分配发送缓冲区*/ reqbuf = (char *)tpalloc("STRING",NULL,reqlen); if ( reqbuf == (char *)NULL) { printf("tpalloc failed\n"); tpterm(); } strcpy(reqbuf,"1000"); /*调用 TUXEDO 的服务 TEST*/ if (tpcall("TEST", (char *)reqbuf, 0L, (char **)&reqbuf, (long *)&reqlen, 0< 0 )

{ printf("tpcall failed,tperrno=%ld,tperrtext=%s\n",tperrno,tpstrerror(tperrno)); tpfree(reqbuf); tpterm(); exit(1); } printf("name=%s\n",reqbuf); tpfree(reqbuf); tpterm(); return(0); }


五、编译服务端程序 1.用 ORACLE 的 PROC 把 test.pc 文件预编译成 test.c 文件 d:\test> proc test.pc include=%TUXDIR%/include 2.用 buildserver 把 test.c 编译成可执行文件,注意-r 后带的 Oracle_XA 与 RM 文件中 的一致。 d:\test> buildserver -o test -f test.c -r Oracle_XA -s TEST 六、编译客户端程序 d:\test> buildclient -o testcli -f testcli.c 七、启动 TUXEDO 应用系统 应能看到所有的 SERVER 都启动成功.这时,我们的服务端程序 test 会自动与 ORACLE 数据库 建立连接,并一直保持这个连接,直到 TUXEDO 系统或 ORACLE 数据库关闭.所以在我们的程序 test.pc 中看不到与数据库连接的语句,因为现在与 数据库的连接由 TUXEDO 自动管理.如果 TMS_ORA8i 启动失败会在当前目录生成一个 *.trc 文件,记录失败的原因,同时 TUXEDO 的 ULOG 文件中也会有一些错误信息.可参考这些错误信息.进行错误分析. d:\test> tmboot -y exec TMS_ORA8i-A : process id=1072 ... Started. exec TMS_ORA8i-A : process id=528 ... Started. exec test -A : process id=876 ... Started. 八、运行客户端程序,应能看到服务端返回的结果 运行客户端程序, d:\test> testcli name=bill 到此,整个配置过程就大功告成了.ORACLE 的其他版本的配置及在其他操作系统上的配置基

本与本文所述差不多,差别主要在 RM 文件中所连的库文件可能会不样.

9.3 INFORMIX 数据库 XA 的配置 同采用 ORACLE 数据库一样,TUXEDO 服务器可以和 INFORMIX 数据库在同一台服务器上,也 可以在不同的机器上, 如果在不同的机器上, TUXEDO 的服务器上安装一个 INFORMIX 的客 在 户端。从上一节的内容我们知道 TUXEDO 服务器与 INFORMIX 数据库连接有两种方式: 1、 不通过 XA 接口直接互连。 适用于整个系统只有一个数据库的情况。 不需要作任何的配置。 2、通过 XA 接口互连,对整个系统有一个数据库或多个数据库都适用,要进行一些配置工作 才行。下面介绍采用这种互连方式的配置方法。

系统说明: 系统说明: TUXEDO:

版本 TUXEDO6.5(是 32 位的)安装在 HP-UX 11.0 64bit 上, 安装目录 /usr/tuxedo TUXEDO 的例子: /usr/tuxedo/simpdb INFORMIX: 版本 INFORMIX9.21(是 64 位的)安装在 SCO Unix 5.0.5 上, 安装目录 /INFORMIX 数据库名称: mydb TUXEDO 用户名: TUXEDO 注意:如果 TUXEDO 系统是 32 位的,而 INFORMIX 数据库的服务端是 64 是,在 TUXEDO 系 统所在的机器上应安装 INFORMIX 数据库的 32 位的客户端才行。

配置的步骤: 配置的步骤: 一、INFORMIX 的的配置 INFORMIX 1、 数据库一定要以 unbuffered log 方式创建, create database databasename with log; INFORMIX 数据库的 LOG 方式有 3 种:Buffered,Nobuffer,Unbuffered(under buffer) 用 onmonitor 命令可查看数据库是否是用 unbuffered log 方式创建的,log status 那一列为 U 的是 unbuffered log 方式。 用 ontape -s –L 0 –U databasename;可把一个其他方式创建的数据库改 为 unbuffered log 方式的. 2、 tuxedo 用户应该有访问该数据库资源的权限。grant dba to tuxedo; 如果 TUXEDO 用户没有访问该数据库资源的权限, TUXEDO 启动时,TMS 启动会失败, 当 在 ULOG 中会出现类似下面的错误信息: 145053.rs6000!BBL.17510: LIBTUX_CAT:262: INFO: Standard main starting 145053.rs6000!TMS_INFORMIX.20204: 020602: TUXEDO Version 6.5 AIX 2 4 007025954C00. 145053.rs6000!TMS_INFORMIX.20204: LIBTUX_CAT:262: INFO: Standard main starting 145054.rs6000!TMS_INFORMIX.20204: LIBTUX_CAT:466: ERROR: tpopen TPERMERR

xa_open returned XAER_RMERR 145054.rs6000!TMS_INFORMIX.20204: LIBTUX_CAT:250: ERROR: tpsvrinit() failed 145054.rs6000!TMS_INFORMIX.20204: LIBTUX_CAT:300: ERROR: _tlog_open: _gp_tblopen 145054.rs6000!TMS_INFORMIX.20204: LIBTUX_CAT:250: ERROR: tpsvrinit() failed 145054.rs6000!TMS_INFORMIX.20204: LIBTUX_CAT:300: ERROR: _tlog_open: _gp_tblopen: UNIX sys call error - 2 145054.rs6000!tmboot.19178: 020602: TUXEDO Version 6.5 AIX 2 4 007025954C00. 145054.rs6000!tmboot.19178: CMDTUX_CAT:825: ERROR: Process TMS_INFORMIX at simple failed with /T tperrno (TPERMERR resource manager error)


二、TUXEDO 的配置 1. 设置环境变量: (在文件/usr/tuxedo/simpdb/setenv 中) . usr/tuxedo/tux.env INFORMIXDIR=/tmp_mnt/informix/hc; export INFORMIXDIR INFORMIXSERVER=dhc; export INFORMIXSERVER PATH=$TUXDIR/bin:$INFORMIXDIR/bin:/bin:/usr/bin:/usr/ccs/bin:.; export PATH SHLIB_PATH=$SHLIB_PATH:$INFORMIXDIR/lib:$INFORMIXDIR/lib/esql:$INFORMIXDI R/lib/cli:$INFORMIXDIR/lib/c++ :$INFORMIXDIR/lib/client:$INFORMIXDIR/lib/dmi:/usr/lib:/usr/lib/Motif1.2 INCLUDE=$INFORMIXDIR/incl/esql:$INFORMIXDIR/incl:/tuxedo/include:/usr/inc lude; export INCLUDE CFLAGS="-I$INFORMIXDIR/incl -I$INFORMIXDIR/incl/esql" export CFLAGS 2. 重命名下列文件,因为下列文件名与 INFORMIX 中的文件名有冲突,所以要改名。 1.TUXEDO 安装路径 include 目录下的下面文件 把 sqlca.h 改名为 sqlca.h.bbb 把 sqlcode.h 改名为 sqlcode.h.bbb 把 sqlda.h 改名为 sqlda.h.bbb 2.重命名 TUXEDO 安装路径 lib 目录下的下面文件 把 libsql.lib 改名为 libsql.lib.bbb

3. 修改 TUXEDO 安装路径的 udataobj 目录下的 RM 文件,加入: INFORMIX-DSHC:infx_xa_switch:-L/tuxedo/lib -L${INFORMIXDIR}/lib -L${INFORMIXDIR}/lib/esql -lifxa -lifsql -lifasf -lifgen -lifos -lifgls -lnsl -lm -lsec ${INFORMIXDIR}/lib/esql/checkapi.o -lifglx 4. 在 TUXEDO 用户下创建 TMS 文件:TMS_INFORMIX, TUXEDO 通过 TMS_INFORMIX 与 INFORMIX 数据库采用 XA 协议进行通讯

buildtms -r INFORMIX-DSHC -o /tuxedo/bin/TMS_INFORMIX 5. 配置 UBBCONFIG 1. 在*MACHINES 节中增加: TLOGDEVICE = "/usr/tuxedo/simpdb/TLOG" TLOGNAME=TLOG TLOGSIZE=200 2. 改*GROUPS 节的配置为: *GROUPS GROUP1 LMID=simple GRPNO=1 TMSNAME="TMS_INFORMIX" TMSCOUNT=2 OPENINFO="INFORMIX-DSHC:mydb" 修改后的配置文件 ubb 内容如下 IPCKEY 123456 DOMAINID simpapp MASTER simple MAXACCESSERS 100 MAXSERVERS 50 MAXSERVICES 100 MODEL SHM LDBAL N *MACHINES server LMID=simple APPDIR="/usr/tuxedo/simpdb" TUXCONFIG="/usr/tuxedo/simpdb/tuxconfig" TUXDIR="/usr/tuxedo" TLOGDEVICE = "/usr/tuxedo/simpdb/TLOG" TLOGNAME=TLOG TLOGSIZE=100 *GROUPS GROUP1 LMID=simple GRPNO=1 TMSNAME="TMS_INFORMIX" TMSCOUNT=2 OPENINFO="INFORMIX-DSHC:mydb" *SERVERS DEFAULT: CLOPT="-A" test SRVGRP=GROUP1 SRVID=1 *SERVICES

6. 用 TMADMIN 创建 TLOG 文件,TUXEDO 用一个文件 TLOG 记录对数据库操作的日志。用于 协调分布式数据库的提交与回滚.

D:\>tmadmin >crdl -b 500 -z /usr/tuxedo/simpdb/TLOG >crlog -m simple >q 三、服务端的程序:test.cp 服务端的程序: 功能:根据客户端传的 EMPNO 到表 EMP 中取 ENAME 的值,并把它返回给客户端 #include <atmi.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sybhesql.h> #include <sybtesql.h>

EXEC SQL INCLUDE sqlca; EXEC SQL BEGIN DECLARE SECTION; long al_empno=0; char ac_ename[11]=""; EXEC SQL END DECLARE SECTION;
TEST(TPSVCINFO *rqst) { /*接收客户端来的数据*/ al_empno = (FBFR32 *)rqst->data; EXEC SQL select ename into :ac_ename from EMP where empno=:al_empno; if(sqlca.sqlcode!=0) { userlog("select from EMP failure,sqlca.sqlcode=%ld\n",sqlca.sqlcode); tpreturn( TPFAIL, 0, rqst->data, 0, 0 ); } /*把取出的结果返回给客户端*/ strcpy(rqst->data,ac_ename); tpreturn( TPSUCCESS, 0, rqst->data, 0, 0 ); }


四、编写客户端程序: testcli.c 编写客户端程序: 功能:调用 TUXEDO 服务端的服务 TEST,取 EMPNO=1000 所对应的 ENAME 的值,并显示出来 #include <stdio.h>

#include "atmi.h" main(argc, argv) { long reqlen=1024; char *reqbuf; /* 与 TUXEDO 服务端建立连接 */ if (tpinit((TPINIT *) NULL) == -1) { (void) fprintf(stderr, "Tpinit failed\n"); exit(1); } /* 分配发送缓冲区*/ reqbuf = (char *)tpalloc("STRING",NULL,reqlen); if ( reqbuf == (char *)NULL) { printf("tpalloc failed\n"); tpterm(); } strcpy(reqbuf,"1000"); /*调用 TUXEDO 的服务 TEST*/ if (tpcall("TEST", (char *)reqbuf, 0L, (char **)&reqbuf, (long *)&reqlen, 0< 0 ) { printf("tpcall failed,tperrno=%ld,tperrtext=%s\n",tperrno,tpstrerror(tperrno)); tpfree(reqbuf); tpterm(); exit(1); } printf("name=%s\n",reqbuf); tpfree(reqbuf); tpterm(); return(0); }


五、编译服务端程序 1.用 INFORMIX 的 esql 把 test.pc 文件预编译成 test.c 文件 /usr/tuxedo/simpdb/esql -c test.pc -I$INFORMIXDIR/incl/esql 2.用 buildserver 把 test.c 编译成可执行文件,注意-r 后带的 INFORMIX-DSHC 与 RM 文 件中的一致。 /usr/tuxedo/simpdb/buildserver -o simpserv -f simpserver.c -r INFORMIX-DSHC

-s TEST 六、编译客户端程序 /usr/tuxedo/simpdb/buildclient -o testcli -f testcli.c 七、启动 TUXEDO 应用系统 应能看到所有的 SERVER 都启动成功.这时,我们的服务端程序 test 会自动与 INFORMIX 数据 库建立连接,并一直保持这个连接,直到 TUXEDO 系统或 INFORMIX 数据库关闭.所以在我们的 程序 test.cp 中看不到与数据库连接的语句,因为现在与数据库的连接由 TUXEDO 自动管理. /usr/tuxedo/simpdb/ tmboot -y exec TMS_INFORMIX -A : process id=1072 ... Started. exec TMS_INFORMIX -A : process id=528 ... Started. exec test -A : process id=876 ... Started. 八、运行客户端程序 /usr/tuxedo/simpdb/ testcli 应能看到服务端返回的结果: name=bill 到此,整个配置过程就大功告成了.INFORMIX 的其他版本的配置及在其他操作系统上的配置 基本与本文所述差不多,差别主要在 RM 文件中所 连的库文件可能会不样.使用 esql -libs 命令得到应用所需的数据库动态连接库.再加上 INFORMIX 支持 XA 的库 libinfxxa.a,注意在 INFORMIX9.13 之后,libinfxxa.a 的名字变了, 不支持多线程的为 libifxa.a,支持多线程的为 libthxa.a。 如在环境为:AIX 4.3.3, Tuxedo7.1, Informix Dynamic Server 9.21, Esql/C 9.51 中 RM 为: INFORMIX-ONLINE:infx_xa_switch:${INFORMIXDIR}/lib/esql/libifxa.a -L${INFORMIXDIR}/lib -L${INFORMIXDIR}/lib/esql -lifsql -lifasf -lifgen -lifos -lifgls -lnetstub -lc_r -ldl -ltli_r -lm_r ${INFORMIXDIR}/lib/esql/checkapi.o -lifglx 在环境为:AIX 4.3.3, Tuxedo6.5, Informix7.3 中 RM 为: INFORMIX-OnLine:infx_xa_switch:${INFORMIXDIR}/lib/esql/libinfxxa.a -L${INFORMIXDIR}/lib -L${INFORMIXDIR}/lib/esql -lixsqlshr -lixasfshr -lixgenshr -lixosshr -lixglsshr ${INFORMIXDIR}/lib/esql/checkapi.o -lixglx 如果 TMS_INFORMIX 启动不成功,查看 ULOG 文件,根据 ULOG 文件的信息进行排错.

9.4 服务端数据库程序的编程技巧 在编写 TUXEDO 的服务端程序时,大部分的工作是进行数据库方面的编程工作,用数据库提 供的嵌入 SQL 语言的编程接口, ORACLE 的 PROC, 如 INFORMIX 的 ESQL 等编写 TUXEDO 的服务 端程序,下面我们以 PROC 为例,介绍数据库服务端的编程技巧一些经验及应注意的地方。 例子程序: 例子程序: #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sqlda.h> #include <sqlcpr.h>

EXEC SQL INCLUDE sqlca; /*RELEASE_CURSOR=YES 使 PROC 在执行完后释放与嵌入 SQL 有关资源*/ EXEC ORACLE OPTION (RELEASE_CURSOR = YES); EXEC SQL BEGIN DECLARE SECTION; varchar vc_user[20]; long al_empno=0; char ac_ename[11]=""; char ac_hiredate[20]=""; double af_sal=0; EXEC SQL VAR ac_ename EXEC SQL VAR ac_hiredate EXEC SQL END DECLARE SECTION; IS STRING(11); IS STRING(20);
/*错误处理函数*/ void sql_error(char *msg) { printf("\n%s,%ld,%s\n", msg,sqlca.sqlcode,(char *)sqlca.sqlerrm.sqlerrmc); EXEC SQL ROLLBACK RELEASE; exit(-1); }
main()
{ EXEC SQL WHENEVER SQLERROR DO sql_error("ORACLE ERROR: "); /*连接数据库*/ strcpy(vc_user.arr,"scott/tiger@DEMO"); vc_user.len=16; exec sql connect :vc_user; EXEC SQL DECLARE cur_emp CURSOR FOR SELECT EMPNO, ENAME,to_char(HIREDATE,'yyyy/mm/dd hh24:mi:ss'),SAL FROM EMP; EXEC SQL OPEN cur_emp; while(1) { al_empno=0; strcpy(ac_ename,""); strcpy(ac_hiredate,""); af_sal=0; EXEC SQL FETCH cur_emp INTO :al_empno, :ac_ename:ename_ind, :ac_hiredate:hiredate_ind, :af_sal :sal_ind; if( sqlca.sqlcode == 1403) { break; } printf("empno=%ld,ename=%s,hiredate=%s,sal=%lf\n",al_empno,ac_ename,ac_hire date,af_sal); } EXEC SQL CLOSE cur_emp; EXEC SQL ROLLBACK WORK RELEASE; }


1、宿主变量的声明 在 PROC 中,在 SQL 语句中用到的变量称为宿主变量。他们应在 EXEC SQL BEGIN DECLARE SECTION;与 EXEC SQL EDN DECLARE SECTION;之间声明,如上面所示.在声明宿主变量 时应注意以下几点: (1) 在数据库表中定义为 VARCHAR2, VARCHAR, CHAR 的字段, PROC 中可声明为 CHAR, 在 但长度应为它们在表中定义的长度加 1, 因为 PROC 中 CHAR 型变量用’\0’做结尾。 如:ENAME 在表中的定义为 ename varchar2(10),在 PROC 中可定义为: EXEC SQL BEGIN DECLARE SECTION; char ename[11]; EXEC SQL END DECLARE SECTION;

常见错误说明: 如果插入的字符串长度大于 10,如: EXEC SQL INSERT INTO EMP(ENAME) VALUES('12345678901');会出现以下错误: error:ORA-01480: STR 赋值变量缺少空后缀。 如果定义为: EXEC SQL BEGIN DECLARE SECTION; char ename[15]; EXEC SQL END DECLARE SECTION; 当插入的字符串长度大于 10,小于 15 时,如: EXEC SQL INSERT INTO EMP(ENAME) VALUES('12345678901');会出现以下错误: error:ORA-01401: 插入的值对于列过大。 当插入的字符串长度大于 15,如: EXEC SQL INSERT INTO EMP(ENAME) VALUES('12345678901234');会出现以下错误: error:ORA-01401:STR 赋值变量缺少空后缀。 (2) 从 SQL 语句中取字段的值到宿主变量中时,PROC 不会自动给宿主变量去掉右空格。 而是以在 DECLARE SECTION 中定义的长度为准(与表中定义的无关)不足补右空格.如果 不注意这一点,在 PROC 中进行字符串操作时(如比较相等)会出错。如: EXEC SQL BEGIN DECLARE SECTION; char ename[10]; EXEC SQL END DECLARE SECTION; 如果 ENAME 在表中的值为'abc',则取出的值为'abc '; 可用语句 EXEC SQL VAR 重定义 CHAR 型变量。这样宿主变量会自动去掉右空格。如下: EXEC SQL BEGIN DECLARE SECTION; char ename[11]; EXEC SQL VAR ac_ename IS STRING(11); EXEC SQL END DECLARE SECTION; 如果 ENAME 在表中的值为'abc',则取出的值为'abc'; (3) 对浮点型的变量,为保证精度,最好是声明成 DOUBLE 型的.因为 DOUBLE 型的精度比 FLOAT 型高很多. (4) 整型可声明为 LONG 型(对较长的整型,而且所用的平台支持的话,如在 SUN 平台上,可声 明为 LONG LONG 型). (5) DATE 型的处理:DATE 型一般声明为 CHAR(20)。 往表中插入 DATE 型数据时,一般用 TO_DATE()函数进行类型转换,取出值时一般用 TO_CHAR()函数进行类型转换. EXEC SQL select to_char(hiredate,'yyyy/mm/dd hh24:mi:ss') into :ac_hire_date from EMP where empno=1234; EXEC SQL insert into EMP(EMPNO,HIREDATE) values(123,to_date(:ac_hiredate,'yyyy/mm/dd hh24:mi:ss');

2、宿主变量的作用范围 如果宿主变量在所有的函数之外声明,则他们是全局变量。在使用之前要注意把变量的 值初始化,宿主变量也可以在某个函数的内部定义。 这时他们是局部变量。一般都 习惯把宿主变量声明为全局变量。 3、数据库的连接与断开 数据库的连接有以下两种方法: (1) strcpy(vc_user.arr,"scott/tiger"); vc_user.len=11; exec sql connect :vc_user; (2) strcpy(user,"scott"); strcpy(pass,"tiger"); exec sql connect :user identified by :pass; 注意:在有些平台上两种都可以,在有些平台上只能用第一种方法. 在 PROC 程序中,要记住用 EXEC SQL ROLLBACK WORK RELEASE;断开与数据库的连接,并 释放相关的数据库资源。 4、PROC 中的 NULL 值的处理 如果某一字段取出的值是 NULL,会报: sqlcode=-1405, sqlerr=ORA-01405: 读取的列值为 NULL 并且相应的宿主变量的值不会被改变,为执行该 SQL 语句之前的值. 常用的处理 NULL 值的方法有: (1) 采用指示器变量,此时不会有-1405 错误,当必须是所以为 NULL 的字段都有相应的指 示器变量,如果某一字段没有指示器变量,但取出的值为 NULL 值,则仍然会有-1405 错误.当取出的值是 NULL 时,相应的指示器变量变量为-1,可根据指示器变量的值 做相应的处理。 (2) 如果字段较多,可取字段到一个结构体中及与该结构体对应的指示器结构体中.如上 面的例子中可定义结构体: struct str_emp{ long al_empno; char ac_ename; char ac_hiredate; double af_sal; }; struct str_emp_ind{ long al_empno; char ac_ename; char ac_hiredate; double af_sal;

}; struct str_emp str_emp; strcut str_emp_ind str_emp_ind; 在取之前可用 memset(&str_emp,0,sizeof(str_emp)).清空该结构体,这样如果是字 符型的 NULL,会为"",整型的 NULL 会为 0,浮点型的会为 0.00。 此时不会有-1405 错误。 (3) 也可采用 NVL()函数:举例如下: EXEC SQL DECLARE authors CURSOR FOR SELECT EMPNO, NVL(ENAME,chr(0)), nvl(to_char(HIREDATE,'yyyy/mm/dd hh24:mi:ss'),chr(0)),NVL(SAL,0) FROM EMP; 这样也不会有-1405 错误不,当取出的值是 NULL 时,自动用 NVL()中指定的值代替. CHR(0)也可直接用''代替,如下: SELECT EMPNO, NVL(ENAME,''), nvl(to_char(HIREDATE,'yyyy/mm/dd hh24:mi:ss'),''),NVL(SAL,0) FROM EMP;

5、PROC 中的错误的处理 所有的 SQL 语句都有可能出错.所以都要加以判断,但每个 SQL 语句后都加错误判断,太麻 烦,可用一个函数如 sql_error()来进行错误处理, 方法: (1)定义 ql_error()函数。 (2)在开头加上 EXEC SQL WHENEVER SQLERROR DO sql_error();这样当发生 sqlca.sqlcode <0 的错误时,程序自动转到 sql_error()中执行. 注意:对 sqlca.sqlcode >0 的错误如 sqlca.sqlcode =1403 是不会转到 sql_error() 中执行的. 另外:在 UNIX 下,可以用 OERR 来查找错误的描述。如: ora ORA -1405 查找错误号 为-1405 的描述.

6、PROC 中调用存储过程的方法 要把存储过程放在 EXEC SQL EXECUTE 和 END-EXEC;之间,如下所示: 其中:al_empno,ac_ename 为输入参数,l_return,l_errno,c_errtext 为输出参数。 al_empno=8888; strcpy(ac_ename,"ABCD"); EXEC SQL EXECUTE BEGIN up_db_emp(:al_empno,:ac_ename,:l_return,:l_errno,:c_errtext); END; END-EXEC; if (l_return != 0) { printf("call up_db_emp fail,errno=%ld,errtext=%s\n",l_errno,c_errtext);

}

的命令行选项: 7、PROC 的命令行选项: PROC 编译器有很多的命令行选项,在命令行下直接不带参数运行 PROC,会列出所有的命令 行选项来,并有说明。 (1)储存过程:编译储存过程是要带上用户名及密码 proc USERID=scott/tiger sqlcheck=SEMANTICS ireclen=512 iname=test.cpp (2)PARSE=NONE 对非 SQL 代码不进行语法分析,默认对非 SQL 代码也进行语法分析. 在 RED HAD6.3 上的 ORACLE8.1.5 中用 PROC 时,会提示:/USR/INCLUDE/STDIO.H 及其他 的.H 文件中有错. 可把 PARSE=NONE 加上,就好了.

YES); 8、注意加上:EXEC ORACLE OPTION (RELEASE_CURSOR = YES) 注意加上: RELEASE_CURSOR=YES 使 PROC 在执行完后释放与嵌入 SQL 有关资源,保证在该 PROC 程 序执行完后,ORACLE 不会锁住数据库资源,如锁表等。 如果在 PROC 中用到 ORACA,还要在程序头加上: EXEC ORACLE OPTION (ORACA=YES); 9、PROC 中的类型转换 一、在 C 语言中: (1)字符型到整型可用 ATOI() ATOL(),SSCANF() (2)整型,浮点型到字符型,可用 SPRINTF() (3)字符型到浮点型用 ATOF()不行,最好用 SSCANF(),举例如下: EXEC SQL BEGIN DECLARE SECTION; double d_demo; float f_demo; char ac_text[20]="222"; EXEC SQL END DECLARE SECTION; (1)sscanf(ac_text, "%f", &d_demo); printf("ac_text=%s,d_demo=%f\n",ac_text,d_demo); (2)sscanf(ac_text, "%lf", &d_demo); printf("ac_text=%s,d_demo=%f\n",ac_text,d_demo); (3)sscanf(ac_text, "%f", &d_demo); printf("ac_text=%s,d_demo=%lf\n",ac_text,d_demo); (4)sscanf(ac_text, "%lf", &d_demo); printf("ac_text=%s,d_demo=%lf\n",ac_text,d_demo); printf("*******************\n");

(5)sscanf(ac_text, "%f", &f_demo); printf("ac_text=%s,f_demo=%f\n",ac_text,f_demo); (6)sscanf(ac_text, "%lf", &f_demo); printf("ac_text=%s,f_demo=%f\n",ac_text,f_demo); (7)sscanf(ac_text, "%f", &f_demo); printf("ac_text=%s,f_demo=%lf\n",ac_text,f_demo); (8)sscanf(ac_text, "%lf", &f_demo); printf("ac_text=%s,f_demo=%lf\n",ac_text,f_demo); 输出的结果: ac_text=222.00,d_demo=0.000000 ac_text=222.00,d_demo=222.000000 ac_text=222.00,d_demo=222.000032 ac_text=222.00,d_demo=222.000000 ******************* ac_text=222.00,f_demo=222.000000 ac_text=222.00,f_demo=0.000000 ac_text=222.00,f_demo=222.000000 ac_text=222.00,f_demo=0.000000 d_demo=atof(ac_text); printf("ac_text=%s,atof(ac_text)=%f\n",ac_text,d_demo); d_demo=atof(ac_text); printf("ac_text=%s,atof(ac_text)=%lf\n",ac_text,d_demo); f_demo=atof(ac_text); printf("ac_text=%s,atof(ac_text)=%f\n",ac_text,f_demo); f_demo=atof(ac_text); printf("ac_text=%s,atof(ac_text)=%lf\n",ac_text,f_demo); 输出的结果: ac_text=222.00,atof(ac_text)=1243288.000000 ac_text=222.00,atof(ac_text)=1243288.000000 ac_text=222.00,atof(ac_text)=1243288.000000 ac_text=222.00,atof(ac_text)=1243288.000000 从上面的结果可见: DOUBLE 型应采用 sscanf(ac_app_capcity, "%lf", &d_app); 打印用"%lf","%f" 都可 以. (2),(4)正确 FLOAT 型应采用 sscanf(ac_app_capcity, "%f", &d_app); 打印用"%lf","%f" 都可以.

(5),(7)正确 采用 ATOF()转换的结果都是错的,所以不要用它。 二、写表或从表中取数据时: (1)字符型与整型之间可不用转换,采用默认方式。 (2)字符型与浮点型之间可不用转换,采用默认方式。 (3)日期型与字符型之间可用 TO_CHAR(),TO_DATE()。

10、 10、PROC 中的 4 种动态 SQL 简介 (1)动态 SQL1: 不能是查询(SELECT)语句,并且没有宿主变量. 用法:拼一串动态 SQL 语句,并用 EXECUTE IMMEDIATE 执行,如: EXEC SQL EXECUTE IMMEDIATE "CREATE TABLE dyn1 (col1 VARCHAR2(4))"; (2)动态 SQL2: 不能是查询(SELECT)语句,并且输入的宿主变量数目是知道的, 用法:拼一串动态 SQL 语句,用 PREPARE,EXECUTE 语句执行. strcpy(c_sql, "DELETE FROM EMP WHERE EMPNO = :?"); EXEC SQL PREPARE sql_stmt FROM :c_sql; EXEC SQL EXECUTE sql_stmt USING :emp_number; (3)动态 SQL3: 用于创建动态查询, 并且要查询的字段及输入的宿主变量数目是知道的 用法: 拼一串动态 SQL 语句,用 PREPARE 分析该语句,并要定义一个 CURSOR 进行取值 如 : 如 要 查 询 的 数 据 按 一 年 12 月 放 到 12 张 表 中 。 表 名 为 user_fee_1mon, user_fee_2mon,....可采用动态 SQL3 来进行查询 strcpy(c_sql,"select c_user_id,c_user_name, to_char(t_date,'yyyy/mm/dd hh:mi:ss'),n_fee\n"); strcat(c_sql,"from USER_FEE_"); strcat(c_sql,ac_mon); strcat(c_sql," \n where c_user_id = :v1"); EXEC SQL PREPARE s FROM :c_sql; EXEC SQL DECLARE cur_user_fee CURSOR FOR s; EXEC SQL OPEN cur_user_fee USING :ac_user_id; while(1) { EXEC SQL FETCH cur_user_fee into :c_user_id,:c_user_name,:c_date,:n_fee); if (sqlca.sqlcode < 0) { /*FETCH CURSOR 失败*/

printf("fetch cursor cur_user_fee \ fail,sqlcode=%ld,sqlserr=%s",sqlca.sqlcode,sqlca.sqlerrm.sqlerrmc); } if( sqlca.sqlcode == SQLNOTFOUND) { break; } } EXEC SQL CLOSE cur_user_fee; (4)动态 SQL4:要处理的字段及输入的宿主变量数目和主变量的类型事先是不知道的,如: INSERT INTO EMP (<unknown>) VALUES (<unknown>) 是最复杂的动态 SQL,很少用,在此不做介绍。

11、SQLCA: 11、SQLCA: SQL 是 ORACLE 的一个结构体,它的域用于最近的一条 SQL 语句执行后的一些信息,如错误 号,错误描述,警告,状态等。常用的域介绍如下: SQLCA.sqlcode:错误号,=0 正确,=1403 没取到数据 SQLCA.sqlserrm.sqlerrmc:错误描述 SQLCA.sqlerrd[3]:最近的一条 SQL 语句所处理的行数,如果该语句处理失败,则它的值 是不定的,如果错误在一个 CURSOR 操作中发生,则 它的值指已成功处理的行数.在 DELETE,UPDATE 中,它不包含因外键约束而删除,更新的 那些行, DELETE FROM EMP WHERE DEPT='SALE'; 在表 EMP 中删除 20 行,但如果表 EMP 与表 ADDRESS 有外键约束,导致表 ADDRESS 也被删 除 20 行,则 SQLCA.sqlerrd[3]=20,而不是 40。

12、 12、其他方面 因为 PROC 编程实际时在 C 语言中嵌入 SQL 语句,所以应注意 C 语言编程的一些常见错误, 如缓冲区溢出,数组越界,内存泄露等问题。而这些错误可能会使所在的进程死掉,或耗光 系统的内存,并且它们不容易在测试中发现,所以要特别注意。 如下面的程序,因为 ac_ename 声明的长度为 11,最多只能给它 10 个字符,而程序中给它 赋 18 个字符,会造成越界。 EXEC SQL BEGIN DECLARE SECTION; char ac_ename[11]=""; EXEC SQL VAR ac_ename IS STRING(11); EXEC SQL END DECLARE SECTION; strcpy(ac_ename,"123456789123456789"); 下面的程序,指针 str1 没有进行初始化,就给它赋值,会造成缓冲区溢出错误。 char *str1; strcpy(str,"111111111111111");

正确的用法是: char *str1; str1=(char *)malloc(100); strcpy(str,"111111111111111"); 另外在程序中用 malloc()等分配的内存,一定要记住用 free()释放掉,否则会造成内 存泄露。

9.5 事务处理应注意的一些问题 1. 不要嵌套事务 如下面的程序在(a)处用 tpbegin 发起一个全局事务之后,在(b(和(c)处又发起一个事务, 一般的数据库是不允许做事务当中嵌套事务这样的操作。在编程中应注意避免这种情况。 DEMO(TPSVRINFO *rqst) { …… tpbegin( ); /*发起一个全局事务*/ if(...) { EXEC SQL begin; /*又发起一个局部的事务) …… EXEC SQL update …… if(sqlcode) EXEC SQL commit;/*提交*/ else EXEC SQL roback; /*回滚*/ …… } else { tpbegin(); /*又发起一个全局的事务) */ ...... tpcommit();/*提交*/ ...... } If(tpcommit( )==-1) {/*提交*/ …. Tpabort( ); /*回滚*/ ….. } ……. tpreturn(TPSUCCESS, …..); } (c) (b)

(a)


2. 最好不要在客户端开始一个事务 可以在客户端,包括本地 CLENT 与远程 CLEINT,开始一个事务,也可以在服务端开始一个事 务.在客户端开始一个事务的 做法一般如下, .... tpbegin() tpcall(service1) tpcall(service2) .... tpcommit() .. 在客户端开始的事务,不能象在服务端开始事务那样在程序中直接嵌入 SQL 语句对数据库进 行操作,它只能通过调用 SERVICE. 在 SERVICE 中数据库进行操作. 通常的做法是尽量在服务端开始事务,因为在客户端处理事务有很多缺点. 1. 客户端与服务端之间的网络通讯要花很多时间,而且如果网络不可靠时. 2. 如果在 CLIENT 开始一个事务,并且处理该事务的过程中要与用户进行交互,如果用户离 开或并继续往下走,那么该事务就悬挂在那里. 3. 把事务控制放在客户端,会使客户端的程序与业务相关,与 3 层结构的设计思想相矛盾. 4. 把事务放在客户端,会使系统对并发的事务数失去控制,如: 原来有 50 个 SERVER,那么 最多可能有 50 个事务存在,如果该系统同时可以有 2000 个 CLIENT 连到 TUXEDO SERVER 上,那么最多可能会启动 2000 个事务,会超出系统的限制. 5. 客户端的机器不可靠.在处理事务过程中可能死机等.

3. 跨越多个资源管理器的事务处理 在 TUXEDO 中,资源管理器与 GROUP 使一一对应关系,所以如果一个全局事务要跨越多个资 源管理器,那么它必然也要跨越多个 GROUP,而一个 SERVER 只能

1. 在客户端开始该事务,在 tpbegin()与 tpcommit()之间调用这些 SERVICE .... tpbegin() tpcall(service1) /*在 GROUP1 上,与资源管理器 1 连接*/ tpcall(service2) /*在 GROUP2 上,与资源管理器 2 连接*/ tpcall(service3) /*在 GROUP3 上,与资源管理器 3 连接*/ .... tpcommit() ..

2.在服务端开始该事务 SERVICE1(TPSVCINFO *rqst) {.... tpbegin() tpcall(service1) /*在 GROUP1 上,与资源管理器 1 连接*/ tpcall(service2) /*在 GROUP2 上,与资源管理器 2 连接*/ tpcall(service3) /*在 GROUP3 上,与资源管理器 3 连接*/ .... tpcommit() .. } 在客户端调用 SERVICE1

4.事务的超时时间的设置 事务的超时时间的设置 在数据库编程中要合理的设置超时时间,下面通过例子来说明。 例子的 UBBCONFIG 文件内容为: *RESOURCES IPCKEY 123456 DOMAINID simpapp MASTER simple MAXACCESSERS 30 MAXSERVERS 5 MAXSERVICES 10 MODEL SHM LDBAL N SCANUNIT 10 SANITYSCAN 1 BLOCKTIME 4

*MACHINES MYSERVER LMID=simple APPDIR="d:\simpdb" TUXCONFIG="d:\simpdb\tuxconfig" TUXDIR="d:\tuxedo65" TLOGDEVICE = "d:\simpdb\TLOG" TLOGNAME=TLOG
TLOGSIZE=100 MAXWSCLIENTS=5
*GROUPS GROUP1 LMID=simple GRPNO=1 OPENINFO="Oracle_XA:Oracle_XA+Acc=P/scott/tiger+SesTm=600+MaxCur=5+LogDir= ." TMSNAME="TMS_ORA8i" TMSCOUNT=2
*SERVERS DEFAULT: CLOPT="-A" test SRVGRP=GROUP1 SRVID=1
WSL
SRVGRP=GROUP1 SRVID=12 CLOPT="-A -- -n //XCJ:8888 -m 5 -M 10 -x 8"
*SERVICES
的内容为: 客户端的程序为 testcli.c 的内容为 #include <stdio.h> #include "atmi.h"
int main(int argc, char *argv[]) { char *sendbuf; long rcvlen; int ret; if (tpinit((TPINIT *) NULL) == -1) { (void) fprintf(stderr, "Tpinit failed\n"); exit(1); }
      if((sendbuf = (char *) tpalloc("STRING", NULL, 1000)) == NULL) {
(void) fprintf(stderr,"Error allocating send buffer\n"); tpterm(); exit(1); } ret = tpcall("TEST", (char *)sendbuf, 0, (char **)&sendbuf, &rcvlen, TPNOBLOCK); if(ret == -1) { printf("tperrno=%ld,tpstrerr=%s\n",tperrno,tpstrerror(tperrno)); tpfree(sendbuf); tpterm(); exit(1); } tpfree(sendbuf); tpterm(); return(0); }


(1) 如果服务端 test.cpp 的内容为: 的内容为: TEST(TPSVCINFO *rqst) { if(tpbegin(60,0)==-1) { printf("tpbegin() fail\n"); tux_return(sendbuf,"update emp fail",sqlca.sqlcode,(char *)sqlca.sqlerrm.sqlerrmc); } EXEC SQL update emp set ename=:ename where empno=7369; if (sqlca.sqlcode != 0) { tux_return(sendbuf,"update emp fail",sqlca.sqlcode,(char *)sqlca.sqlerrm.sqlerrmc); } if(tpcommit(0) == -1) { printf("tpcommit() fail=%s\n",tpstrerror(tperrno)); exit(1); } userlog("*****************begin sleep\n"); sleep(150); userlog("*****************end sleep\n"); tpreturn( TPSUCCESS, 0, (char *)sendbuf, 0, 0 ); }

1.如果 testcli.c 中的 tpcall()的 flag 的值为 . 的值为:TPNOBLOCK, 那么 的 客户端的输出为: 客户端的输出为 The current time is: 11:27:51.54 D:\tuxdemo\simpdb>testcli tpcall failed: tperrno=13,tpstrerror=TPETIME - timeout occured The current time is: 11:28:32.70 文件中的输出为: 服务端的 ULOG 文件中的输出为 112751.XCJ!test.1376: *****************begin sleep 113021.XCJ!test.1376: *****************end sleep 结论: 结论 客户端从调用 TPCALL()开始,过了约 40 秒,客户端超时出错,但服务端的 TEST 将继续 执行.所以数据库的更新是成功的,但该客户端在本次调用中无法知道这一结果.

2.如果 testcli.c 中的 tpcall()的 flag 的值为 . 的值为:TPNOTIME,那么 的 , 客户端的输出为: 客户端的输出为 The current time is: 11:33:49.54 D:\tuxdemo\simpdb>testcli fdf The current time is: 11:36:30.89 文件中的输出为: 服务端的 ULOG 文件中的输出为 113400.XCJ!test.1376: *****************begin sleep 113630.XCJ!test.1376: *****************end sleep 结论: 结论 该客户端将不管系统的超时设置,阻塞在 tpcall()调用上,只到该服务端有结果返回为 止.该客户端在本次调用中知道数据库更新操作的结果.

的内容为: (1)如果服务端 test.cpp 的内容为: ) TEST(TPSVCINFO *rqst) { if(tpbegin(60,0)==-1) { printf("tpbegin() fail\n"); exit(1); } userlog("*****************begin sleep\n");

sleep(150); userlog("*****************end sleep\n"); EXEC SQL update emp set ename=:ename where empno=7369; if (sqlca.sqlcode != 0) { tux_return(sendbuf,"update emp fail",sqlca.sqlcode,(char *)sqlca.sqlerrm.sqlerrmc); } if(tpcommit(0) == -1) { printf("tpcommit() fail=%s\n",tpstrerror(tperrno)); exit(1); } tpreturn( TPSUCCESS, 0, (char *)sendbuf, 0, 0 ); }


1.testcli.c 中的 tpcall()的 flag 的值为 . 的值为:TPNOBLOCK,那么 的 , 客户端的输出为: 客户端的输出为 The current time is: 20:59:51.30 D:\simpdb>testcli tperrno=13,tpstrerr=TPETIME - timeout occured The current time is: 21:00:32.97 文件中的输出为: 服务端的 ULOG 文件中的输出为 205951.XCJ!test.1456: 全局事务 rid x0 x3c8b46f6 x5: *****************begin sleep 210221.XCJ!test.1456: 全局事务 rid x0 x3c8b46f6 x5: *****************end sleep 210221.XCJ!test.1456: tpcommit() fail=TPEABORT - transaction cannot commit 结论: 结论 客户端从调用 TPCALL()开始,过了约 40 秒,客户端超时出错,当服务端的 TEST 将继续 执行.在 tpbegin()中为该事务设置的超时为 60 秒,但过了 150 秒之后才开始进行数据库操 作,那么该事务将因为超时而回滚, ULOG 的输出可以看到这一点.所以数据库的更新 从 是失败的,该客户端在本次调用中无法知道这一结果. 如果把在 tpbegin()中为该事务设置的超时为 160 秒,那么数据库的更新操作将时成功的.

2.testcli.c 中的 tpcall()的 flag 的值为 . 的值为:TPNOTIME,那么 的 , 客户端的输出为: 客户端的输出为 The current time is: 21:05:09.94 D:\simpdb>testcli tperrno=11,tpstrerr=TPESVCFAIL - application level service failure The current time is: 21:07:40.77

文件中的输出为: 服务端的 ULOG 文件中的输出为 210510.XCJ!test.1456: 全局事务 rid x0 x3c8b46f6 x6: *****************begin sleep 210740.XCJ!test.1456: 全局事务 rid x0 x3c8b46f6 x6: *****************end sleep 210740.XCJ!test.1456: tpcommit() fail=TPEABORT - transaction cannot commit 结论: 结论 客户端从调用 TPCALL()开始,过了约 150 秒,服务端的 SERVICE:TEST 死掉.该客户 端出错退出,服务端的结果和上面的一样。

从上面的例子可以看出: 从上面的例子可以看出: (1)tpbegin()中设置的超时时间一定要大于该事务的实际处理实际,否则该事务将因为超 时而回滚, (2)客户端的超时时间一定要大于事务的处理时间,否则该客户端在服务端的处理结果还 没有返回之前就会超时出错,无法知道服务端事务的处理结果

当事务由客户端发起,会出现以下问题: 当事务由客户端发起,会出现以下问题: 1.本地客户机发起事务,并指定超时时间,发送请求后睡眠,用 tmadmin 工具查看,事务 会在超时过后被标记并回退。

2.本地客户机发起事务,并指定超时时间,发送请求后被中断,用 tmadmin 工具查看,事 务会立刻退出。

3.远程客户机发起事务,并指定超时时间,发送请求后无论睡眠或被中断,用 tmadmin 工 具查看,事务都会在超时过后被标记并回退。同第一种情况。




赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论