重现ORA-021500(17099)错误,并提供解决办法

    两天前写的一篇文章提到公司的应用程序常常出现ORA-21500错误(http://blog.csdn.net/ah__fu/archive/2008/04/16/2296641.aspx),在网上找了很久,终于有以下资料提供了些头绪:

http://www.dbatools.net/doc/bug10204.html     Bugs fixed in the 10.2.0.4 Patch Set
http://blogs.sun.com/mandalika/tags/oracle     Solaris/SPARC: Oracle 11gR1 client for Siebel 8.0

    从BLOG http://blogs.sun.com/mandalika/tags/oracle 的最后几句话:
Although I'm not sure what exactly is the underlying issue for the core dump, my suspicion is that there is some memory corruption in Oracle client's code; and the Siebel Object Manager crash is due to the Oracle bug 5682121 - Multithreaded OCI clients do not mutex properly for LOB operations. The fix to this particular bug would be available in Oracle 10.2.0.4 release; and is already available as part of Oracle 11.1.0.6.0. In case if you notice the symptoms of failure as described in this blog post, upgrade the Oracle client in the application-tier to Oracle 11gR1 and see if it brings stability to the Siebel environment.
    得知,这个专家认为这个错误是ORACLE客户端的BUG导致的,必须要将客户端升级到ORACLE 10G才能解决。(P.S 公司使用的ORACLE版本是9.0.4)

   文档http://www.dbatools.net/doc/bug10204.html 中搜索ORA-21500也发现一行这样的信息:
       BUG ID; 5682121
       Bug: Multithreaded OCI clients do not mutex properly for LOB operations (ORA-21500 [17099])
           我们的代码也的确是在多线程环境下使用PRO*C导致了ORA-21500(P.S PRO*C实际上只是预编译器,仍然需要将PC的代码转换成对OCI的调用,ORACLE应该只用一种API接口,那就是OCI)

   因此,到处寻找ORACLE 10G Client for Linux的光盘,找了半天也没找到。没办法,只好自己想办法让这个BUG重现,便于确定是否是自己的代码的问题。
    出问题的代码在于自己封装了一个数据库的连接池,其原理是预先分配N个连接,然后每个线程需要连接的时候从连接池中获取一个空闲连接来使用,使用完后立即将连接释放回连接池。这样,每个线程不必一直占用一个连接,也不必每次都直接连接数据库。连接池的代码很简单,看了几遍也没发现哪儿有问题。于是编写了以下的代码来模拟连接池的原理,尝试让ORA-21500重现。
// ORA21500.pc
#ifndef ORA_PROC
    #include 
< stdio.h >
    #include 
< string .h >
    #include 
< stdlib.h >
    #include 
< pthread.h >
    
//  必须先定义这个宏,避免在每个PC文件中生成全局的sqlca变量
     #define  SQLCA_NONE
    #include 
< sqlca.h >
#endif

#define  MAX_CONN 4
#define  CONN_STR "webuser/webuser@bsmp"

/**/ /// 去除PROC函数内的“变量未使用”的警告的宏
#define  NO_WARNING {sqlstm.sqlvsn = sqlstm.sqlvsn;}

/**/ /// 分配一个全局的连接池
EXEC SQL BEGIN DECLARE SECTION;
sql_context ctx[MAX_CONN];
EXEC SQL END DECLARE SECTION;

/**/ /// 线程函数
void *  read_data( void *  param)
... {
    EXEC SQL BEGIN DECLARE SECTION;
    
struct sqlca sqlca;
    
int* index = (int*)param;
    
int nCount = 0;
    
int user_id;
    EXEC SQL END DECLARE SECTION;
    printf(
"%s %d: index=%d thread=%d ", __FUNCTION__, __LINE__, *index, (int)pthread_self());
    EXEC SQL CONTEXT USE :ctx[
*index];

    EXEC SQL DECLARE CurUser CURSOR FOR 
        SELECT user_id FROM webuser.user_info WHERE rownum
<=100000;
    EXEC SQL OPEN CurUser;
    
do
    
...{
        EXEC SQL FETCH CurUser INTO :user_id;
        nCount
++;
    }
 while(sqlca.sqlcode==0);
    EXEC SQL CLOSE CurUser;
    printf(
"%s %d: user count=%d index=%d thread=%d ", __FUNCTION__, __LINE__, 
        nCount, 
*index, (int)pthread_self());
    
return NULL;
    NO_WARNING;
}


/**/ /// 创建连接池和线程
void  test()
... {
    EXEC SQL BEGIN DECLARE SECTION;
    
struct sqlca sqlca;
    
int i;
    
char ConnStr[100];
    
int index[MAX_CONN];
    EXEC SQL END DECLARE SECTION;
    pthread_t threads[MAX_CONN];
    strcpy(ConnStr, CONN_STR);
    memset(threads, 
0sizeof(threads));
    
/**//// 创建连接池
    for (i=0; i<MAX_CONN; i++)
    
...{
        EXEC SQL CONTEXT ALLOCATE :ctx[i];
        EXEC SQL CONTEXT USE :ctx[i];
        EXEC SQL CONNECT :ConnStr;
        
if (sqlca.sqlcode != 0)
        
...{
            printf(
"%s %d: 连接数据库错误:code=%d, errm=%s ", __FUNCTION__, __LINE__,
                sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
            
return;
        }

    }

    
//创建线程
    for (i=0; i<MAX_CONN; i++)
    
...{
        index[i] 
= i;
        
if (0!=pthread_create(threads+i, NULL, read_data, index+i))
        
...{
            printf(
"%s %d: create thread failed! ", __FUNCTION__, __LINE__);
        }

        
else
        
...{
            printf(
"%s %d: thread[%d]=%d ", __FUNCTION__, __LINE__, i, (int)threads[i]);
        }

    }

    
//连接线程
    for (i=0; i<MAX_CONN; i++)
    
...{
        pthread_join(threads[i], NULL);
    }

    printf(
"%s %d: end ", __FUNCTION__, __LINE__);
    
return;
    NO_WARNING;
}


int  main()
... {
    test();
    
return 1;
}


/**/ /*
proc userid=webuser/webuser@bsmp parse=partial code=cpp sqlcheck=full threads=yes char_map=string def_sqlcode=false oraca=false objects=false errors=true lines=true auto_connect=no ltype=none hold_cursor=yes release_cursor=no iname=ORA21500.pc oname=ORA21500.cpp cpool=yes cmax=5 cmin=3 cincr=2 ctimeout=2 cnowait=7 
g++ -o ORA21500.o -c ORA21500.cpp -g -Wall -Werror -I"${ORACLE_HOME}/precomp/public"
g++ -o ORA21500.exe ORA21500.o  "${ORACLE_HOME}/lib/libsql9.a" "${ORACLE_HOME}/lib/libclntsh.so" -lpthread
*/

    代码的最后几行是编译命令。编译后执行这个程序,几乎每次都发生core dump, 但是出现ORA-21500信息的次数较少。以上的代码实际上也就是我封装的连接池的原理所在。如此简单的代码也能崩溃掉,看来这真的是ORACLE客户端的BUG无疑了。

    正当打算将客户端安装成ORACLE 10g的时候,突然想到修改一下PRO*C的编译参数试试。
    于是将proc编译参数中的连接池信息去掉:cpool=yes cmax=5 cmin=3 cincr=2 ctimeout=2 cnowait=7
    再编译执行以上代码,结果让我大跌眼睛(还好我不戴眼睛)————居然运行完全正确,没有发生core dump!
    恩,从上面的现象看来,ORA-21500内部错误这个BUG与ORACLE的连接池机制有关。关闭连接池暂时可以屏蔽掉这个错误,但是不能肯定在其他环境下不会出现。

   OK,综上所述:
1、在PRO*C的编译选项中,如果使用了连接池,多线程下会出现ORA-21500错误;去掉连接池选项即可解决;
2、直接调用OCI代码,在多线程和连接池同时使用的情况下,也可能会出现这个错误;
3、不管怎么样,ORA-21500[17099]始终是一个BUG,最好还是换成ORACLE新版的产品。

你可能感兴趣的:(多线程,oracle,sql,c,linux)