也就是说,以上的
rsR是直接调用远程存取过程返回的一个记录集,
打开方式为adOpenStatic,锁方式为adLockReadOnly
RS是通过CommFunc.dll打开的本地记录集,
打开方式为adOpenDynamic,锁方式为adLockOptimistic
但这个地方泄露的是rsR
以上的Set rsR = fCenterCmdExecute(cmdR)中
fCenterCmdExecute是个函数,私有的,定义就是记录集,所以返回也就是记录集,这个函数主要打开数据库,执行存取过程,返回记录集

解决方案 »

  1.   

    没法做测试,不过感觉上应该是rs对象的问题。我写过很多类似的东西,都没法发现过问题你再检查一下fCenterCmdExecute函数
      

  2.   

    : KingSunSha(弱水三千) ,我想这个问题与oracle有关,你是否可以测试一下,因为我这几天一直都在测试,测试结果表明,只要是用oracle的返回记录集,内存就泄露,我不说fCenterCmdExecute,我把程序写成以下还是泄露。Public Function fReceiveCandImage(ByVal RemoteID As String, ByVal ResponseID As Long, ocKeyNo As String, oiFingerNo As Integer) As Boolean
            Dim rsR As New ADODB.Recordset
            Dim cmdR As ADODB.Command
            Dim Pram As ADODB.Parameter
            '//写日志
             Call WriteToSendLog("开始接收候补图象信息, ResponseID = " & ResponseID)
            On Error GoTo fReceiveCandImageErr
            '//执行以上过程
            Set cmdR = New ADODB.Command
            With cmdR
                .CommandText = "{call pkgRemote.sp_Get_CandidateImg(?,?,{resultset 0, io_cursor})}"
                .CommandType = adCmdText               'adCmdText
                Set Pram = .CreateParameter("iRemote_ID_I", adBSTR, adParamInput, , RemoteID)
                .Parameters.Append Pram
                Set Pram = .CreateParameter("iResponseID", adInteger, adParamInput, , ResponseID)
                .Parameters.Append Pram
            End With
            '//得到记录集
            rsR.CursorType = adOpenStatic
            rsR.LockType = adLockReadOnly
            Set cmdR.ActiveConnection = CNRemote    'CNRemote是一个ADO的连接
            Set rsR.Source = cmdR
            'set fExecuteOneCommand = cmdR.Execute  '这是另一种执行方式,也泄露
            rsR.Open
            '//如果没有记录集,过程执行不成功
            If rsR Is Nothing Then
                 Call WriteToSendLog("读取远程remote数据库发生错误")
                fReceiveCandImage = False
                GoTo fReceiveCandImageErr
            End If
            '//如果记录集为空,没有取到数据
            If rsR.EOF Then
                 Call WriteToSendLog("没有该图象供接收:ResponseID = " & ResponseID)
                fReceiveCandImage = False
                GoTo fReceiveCandImageExit
            End If
            '//函数执行成功,数据获取成功
            fReceiveCandImage = True
             Call WriteToSendLog("接收候补图像结束")
    fReceiveCandImageExit:
            If rsR.Status = adStateOpen Then rsR.Close
            Set rsR = Nothing
            If Not (cmdR Is Nothing) Then Set cmdR = Nothing
            On Error GoTo 0
            Exit Function
    fReceiveCandImageErr:
             Call WriteToSendLog("接收候补图像失败:" & Err.Description)
            fReceiveCandImage = False
            GoTo fReceiveCandImageExit
    End Function
    ----------------------------------
    你可以仔细看一下以上改写过后的函数,rsR 是在函数内部定义的记录集,直接调用存取过程返回记录集(没有通过别的函数)。
    以上函数仅仅是通过存取过程返回记录集,但是就泄露。如果我的rsR 不是通过存取过程返回,而是在程序中直接打开(用rsR.open "select * from table"),它就不泄露,为什么???
    我很奇怪,也很烦,找不到原因,我的记录集中有long raw的字段,与这个有关系吗?
      

  3.   

    我查遍了msdn(2001,2002),都没有找到原因,
    用ado2.7还是一个样。在msdn的示例代码中,他们返回记录集是用的表类型,然后用光标加一个循环返回回来的,其他的和我的一样。但是我不能那样写,我的表中有long raw类型,如果那样写,大于32k的long raw则没有办法返回回来。我只有返回一个记录集,但是一旦返回记录集,就内存泄露。测试结果表明:
    1、过程返回记录集,泄露
    2、直接打开记录集,不泄露
    3、过程中定义一个行类型,通过光标返回一行(但long raw需要小于32k),不泄露。
    这说明,就是返回记录集的问题
      

  4.   

    这是msdn中有关返回记录集和vb调用存取过程的办法,虽然对于我来说,不实用,但是他却没有说返回记录集,是不是oracle返回记录集本身有问题(bug),而只是大家没有发现罢了。DROP TABLE person;CREATE TABLE person
     (ssn     NUMBER(9) PRIMARY KEY,
      fname   VARCHAR2(15),
      lname   VARCHAR2(20));INSERT INTO person VALUES(555662222,'Sam','Goodwin');INSERT INTO person VALUES(555882222,'Kent','Clark');INSERT INTO person VALUES(666223333,'Jane','Doe');COMMIT;
     /  
    Create the following package on your Oracle server:CREATE OR REPLACE PACKAGE packperson
      AS
        TYPE tssn is TABLE of  NUMBER(10)
        INDEX BY BINARY_INTEGER;
        TYPE tfname is TABLE of VARCHAR2(15)
        INDEX BY BINARY_INTEGER;
        TYPE tlname is TABLE of VARCHAR2(20)
        INDEX BY BINARY_INTEGER;    PROCEDURE allperson
                (ssn    OUT     tssn,
                 fname  OUT     tfname,
                 lname  OUT     tlname);
        PROCEDURE oneperson
            (onessn IN      NUMBER,
             ssn    OUT     tssn,
                 fname  OUT     tfname,
                 lname  OUT     tlname);
    END packperson;
    /  
    Create the following package body on your Oracle server:CREATE OR REPLACE PACKAGE BODY packperson
    ASPROCEDURE allperson
                (ssn    OUT     tssn,
                 fname  OUT     tfname,
                 lname  OUT     tlname)
    IS
        CURSOR person_cur IS
                SELECT ssn, fname, lname
                FROM person;    percount NUMBER DEFAULT 1;BEGIN
        FOR singleperson IN person_cur
        LOOP
                ssn(percount) := singleperson.ssn;
                fname(percount) := singleperson.fname;
                lname(percount) := singleperson.lname;
                percount := percount + 1;
        END LOOP;
    END;PROCEDURE oneperson
          (onessn  IN    NUMBER,
                 ssn     OUT   tssn,
                 fname   OUT   tfname,
                 lname   OUT   tlname)
    IS
     CURSOR person_cur IS
               SELECT ssn, fname, lname
               FROM person
               WHERE ssn = onessn;    percount NUMBER DEFAULT 1;BEGIN
        FOR singleperson IN person_cur
        LOOP
                ssn(percount) := singleperson.ssn;
                fname(percount) := singleperson.fname;
                lname(percount) := singleperson.lname;
                percount := percount + 1;
        END LOOP;
    END;
    END;
    /  
    Open a new project in Visual Basic 5.0 or 6.0 Enterprise edition. Form1 is created by default. 
    Place the following controls on the form:
    Control     Name             Text/Caption
    -----------------------------------------
    Button      cmdGetEveryone   Get Everyone
    Button      cmdGetOne        Get One 
    From the Tools menu, select the Options item. Click the "Default Full Module View" option and then click OK. This will allow you to view all of the code for this project. Paste the following code into your code window:Option Explicit
    Dim Cn As ADODB.Connection
    Dim CPw1 As ADODB.Command
    Dim CPw2 As ADODB.Command
    Dim Rs As ADODB.Recordset
    Dim Conn As String
    Dim QSQL As String
    Dim inputssn As LongPrivate Sub cmdGetEveryone_Click()  Set Rs.Source = CPw1  Rs.Open  While Not Rs.EOF
          MsgBox "Person data: " & Rs(0) & ", " & Rs(1) & ", " & Rs(2)
          Rs.MoveNext
      Wend  Rs.CloseEnd SubPrivate Sub cmdGetOne_Click()  Set Rs.Source = CPw2  inputssn = InputBox("Enter the SSN you wish to retrieve:")  CPw2(0) = inputssn  Rs.Open  MsgBox "Person data: " & Rs(0) & ", " & Rs(1) & ", " & Rs(2)  Rs.CloseEnd SubPrivate Sub Form_Load()  'Replace <User ID>, <Password>, and <Server> with the
      'appropriate parameters.
      Conn = "UID=*****;PWD=*****;driver=" _
             & "{Microsoft ODBC for Oracle};SERVER=dseOracle;"  Set Cn = New ADODB.Connection
      With Cn
          .ConnectionString = Conn
          .CursorLocation = adUseClient
          .Open
      End With  QSQL = "{call packperson.allperson({resultset 9, ssn, fname, " _
             & "lname})}"  Set CPw1 = New ADODB.Command
      With CPw1
          Set .ActiveConnection = Cn
          .CommandText = QSQL
          .CommandType = adCmdText
      End With  QSQL = "{call packperson.oneperson(?,{resultset 2, ssn, fname, " _
             & "lname})}"  Set CPw2 = New ADODB.Command
      With CPw2
          Set .ActiveConnection = Cn
          .CommandText = QSQL
          .CommandType = adCmdText
          .Parameters.Append .CreateParameter(, adInteger, adParamInput)
      End With  Set Rs = New ADODB.Recordset
      With Rs
          .CursorType = adOpenStatic
          .LockType = adLockReadOnly
      End WithEnd SubPrivate Sub Form_Unload(Cancel As Integer)  Cn.Close
      Set Cn = Nothing
      Set CPw1 = Nothing
      Set CPw2 = Nothing
      Set Rs = NothingEnd Sub 
    Go to the Project menu item and select References. Select the "Microsoft Active Data Objects 2.x Library." Run the project. When you click on the "Get Everyone" button, it executes this query:QSQL = "{call packperson.allperson({resultset 9, ssn, fname, "_
                   & "lname})}" -------------------------
    在最后,他们也是执行存取过程,返回一个记录集,这个和我的没有什么区别,区别在于他们使用的是表类型,但怎么返回大于32的字段(如long raw),msdn上没有说,我也不知道
      

  5.   

    谢谢 KingSunSha(弱水三千) :
    我给你原表的创建语句:
    -- Create table
    create table T_CANDIDATEIMG
    (
      RESPONSEID NUMBER(9) not null,
      JOBNO      NUMBER(10),
      CARDTYPE   NUMBER(1),
      KEYNO      VARCHAR2(30) not null,
      FINGERNO   NUMBER(2) not null,
      FINGERGET  CHAR(1),
      ERRORMSG   VARCHAR2(255),
      FPIMAGE    LONG RAW,
      FPIMAGEX   NUMBER(4),
      FPIMAGEY   NUMBER(4),
      FPIMAGELEN NUMBER(6),
      CONSTRAINT PK_CANDIDATEIMG_RESPONSEID PRIMARY KEY (RESPONSEID))
      pctfree 10  pctused 40
      initrans 1  maxtrans 255
      storage
      ( initial 2048K next 2048K
        minextents 1  maxextents 500
        pctincrease 0);
    其中的测试数据,我是用pl/sql develper另外添加上去的,特别是long raw是用pl/sql develper导入的一个32k以上的文件。你也可以生成一条类似的记录。
    过程你可以修改一下
    PROCEDURE sp_Get_CandidateImg(iRemote_ID_I    IN     VARCHAR2,
                                      iResponseID IN     NUMBER,
                                      io_cursor   IN OUT  t_CurGetCandListImg)
        IS
        BEGIN
          --如果有需要处理的数据,则返回需要处理的数据(一个记录集)
            OPEN v_cursor FOR
                SELECT * FROM T_CandidateImg
                WHERE ResponseID = iResponseID;
            io_cursor := v_cursor;
        EXCEPTION
          WHEN OTHERS THEN
            raise;
        END sp_Get_CandidateImg;
    和上面相比,去掉数据检查部分,vb程序就可以用改写后的函数,写日志你就可以不必写了。
      

  6.   

    我简化了一下过程:少用了一个cursor,一个ref cusor。应该比原来的好一些,当然还没有测试过内存泄漏的问题。    /***********************************************************************
         * PROCEDURE:    sp_Get_CandidateImg
         * DESCRIPTION:  get Candidate imgae data
         * PARAMETERS:
                            iRemote_ID_I    IN        VARCHAR2    (1)   远程编号
                            iResponseID     IN        NUMBER      (2)   消息编号
         * RETURNED:
                            io_cursor     IN OUT    t_CurGetCandListImg  返回的记录集
         ***********************************************************************/
        PROCEDURE sp_Get_CandidateImg
                 (iRemote_ID_I    IN        VARCHAR2,
                  iResponseID     IN        NUMBER,
                  ora_status      OUT       VARCHAR2,
                  --该参数用来返回执行状态,那样就不需要通过判断rs来检查执行结果是否正确
                  io_cursor       IN OUT    t_CurGetCandListImg
                 ) IS
            v_Check     NUMBER;
        BEGIN      --检查是否有需要处理的数据
    SELECT COUNT(*) INTO V_CHECK 
              FROM T_MResponse
             WHERE Remote_ID_I     = iRemote_ID_I            
               AND ResponseTYPE    = gnMRCandidateImgTYPE    
               AND ResponseID      = iResponseID;        IF NOT V_CHECK > 0 THEN --如果没有,返回一个空的记录集
                OPEN io_cursor FOR
                    SELECT * FROM T_CandidateImg
                       WHERE 1 = 0 ;
    ora_status := 'NOT FOUND';
    ELSE   --如果有需要处理的数据,则返回需要处理的数据(一个记录集)
            OPEN io_cursor FOR
                SELECT * FROM T_CandidateImg
                WHERE ResponseID = iResponseID;
        ora_status := 'SUCCESS';
        END IF;
            EXCEPTION
               WHEN OTHERS THEN
                 ora_status := SQLERRM;
        END sp_Get_CandidateImg;
      

  7.   

    我自己建了两个很简单的表,只有几个关键列,做了一下测试,但是我这边没法观察到内存泄漏的问题(我对windows下的编程了解甚浅),你能指点一下用什么工具可以检查吗?
    系统环境:WINDOWS XP PRO,VB6+SP5,ORACLE 8.17 AIX VERSION
      

  8.   

    我用的就是windows NT server pack6和windows 2000 server server pack2下做的测试,数据库是oracle 805 server(我不知道与数据库版本有关系没有)。
    vb6(没有打补丁的那种,难道与这个有关系???),我决定打上vb的补丁后再看看。
    --------------------
    测试需要写在循环里,你在程序中单步调式一下记录集打开是否成功,也就是说,是否有较大的数据被打开,不停的打开记录集,关闭记录集就可以了。
    不需要其它的内存检查,在window 任务管理器就可以明显看到,内存使用一直往上加,一直加到几百兆,直到机器死掉。
    如果你的内存释放的话,那内存的使用应当是一升一降才对,也就是说,用存取过程打开记录集时,内存往上涨记录集的大小,记录集关闭的时候,内存释放。内存使用数复原。
    我们这里的情况是,内存只往上涨,不恢复。
      

  9.   

    我也没有好的建议。如果这个程序不需要用户的干预的话,那何不直接用pl/sql写,然后用sqlplus批命令方式来执行呢?这样绝对不会有内存泄漏的问题:)
      

  10.   

    用job肯定不行,因为不止一个过程需要执行,执行的参数也没有办法传递进来。用pl/sql写的话也存在一个问题,有些参数是需要从程序中获取的,如读取文件、访问本地数据库得到的参数等等,我怎么传递给sqlplus批命令。这还不是最麻烦的,最麻烦的是我在pl/sql块中,怎么把大于32K的long raw类型的数据从远程转移到本地数据库。呵呵,既然有问题,那还不是我的问题,我就放心了,现在就是找解决办法。
    办法是可以找到的,我现在唯一想明白的是,这是oracle的错,还是vb的错
      

  11.   

    : biti_rainy(biti_rainy) 
    这个问题,就我的认为,不一定是VB的问题,我现在正在想办法在v c++上去测试,只是我今天还没有找到在c++上调用返回记录集的存取过程的执行方法,不是返回记录集的我会执行,但返回记录集的存取过程我在c++上还真不会执行。我想,这个内存的申请而不释放,有可能是oracle客户端或是ADO,而不是vb,用什么语言无所谓的,而且,vb我还很少见过内存泄露的。