在通过使用微软提供的ODBC SDK读取数据库(SELECT)时,发现Oracle读着读着就读不到数据了(MySQL和SQL Server是正常的),经调试发现SQLExecDirect返回值为-1,在网上查找相关问题时看到一篇文章(ODBC中执行SQLExecDirect报错信息获取),于是就在程序出错的地方加了获取错误信息语句,代码 及 strErrorMsg错误信息分别如下:
#pragma once
#include <sql.h>
#include <string>
#include <list>
#include <map>
using namespace std;
class CDataBaseClient
{
public:
CDataBaseClient(void);
~CDataBaseClient(void);
public:
bool Connect(string strAddr, string strUser, string strPwd);
void DisConnect();
bool IsConnected();
bool ExecuteSql(string strSql);
bool QuerySql(string strSql, list<pair<string, string>> &lstCols);
bool QuerySql(string strSql, list<string> &lstCol, list<string> &lstData);
private:
SQLHENV m_hEnviroment;
SQLHDBC m_hConnection;
};
bool CDataBaseClient::Connect(string strAddr, string strUser, string strPwd)
{
SQLRETURN sqlReturn = SQLAllocEnv(&m_hEnviroment);
if(sqlReturn != SQL_SUCCESS && sqlReturn != SQL_SUCCESS_WITH_INFO)
{
return false;
}
sqlReturn = SQLSetEnvAttr(m_hEnviroment, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0);
sqlReturn = SQLAllocConnect(m_hEnviroment, &m_hConnection);
if(sqlReturn != SQL_SUCCESS && sqlReturn != SQL_SUCCESS_WITH_INFO)
{
SQLFreeEnv(m_hEnviroment);
m_hEnviroment = NULL;
return false;
}
sqlReturn = SQLConnect(m_hConnection, (SQLCHAR*)strAddr.data(), SQL_NTS,(SQLCHAR*)strUser.data(), SQL_NTS,(SQLCHAR*)strPwd.data(), SQL_NTS);
if(sqlReturn != SQL_SUCCESS && sqlReturn != SQL_SUCCESS_WITH_INFO)
{
SQLFreeConnect(m_hConnection);
m_hConnection = NULL;
SQLFreeEnv(m_hEnviroment);
m_hEnviroment = NULL;
return false;
}
return true;
}
void CDataBaseClient::DisConnect()
{
if(m_hConnection)
{
SQLDisconnect(m_hConnection);
SQLFreeConnect(m_hConnection);
m_hConnection = NULL;
}
if(m_hEnviroment)
{
SQLFreeEnv(m_hEnviroment);
m_hEnviroment = NULL;
}
}
bool CDataBaseClient::IsConnected()
{
if(m_hConnection)
{
return true;
}
return false;
}
bool CDataBaseClient::QuerySql(string strSql, list<string> &lstCol, list<string> &lstData)
{
SQLHSTMT hStmt;
SQLRETURN sqlReturn = SQLAllocStmt(m_hConnection, &hStmt);
if(sqlReturn != SQL_SUCCESS && sqlReturn != SQL_SUCCESS_WITH_INFO)
{
return false;
}
sqlReturn = SQLExecDirect(hStmt,(SQLCHAR*)strSql.data(), strSql.length());
if(sqlReturn != SQL_SUCCESS && sqlReturn != SQL_SUCCESS_WITH_INFO)
{
SQLFreeStmt(hStmt, SQL_CLOSE);
return false;
}
lstCol.clear();
lstData.clear();
if(SQLFetch(hStmt) != SQL_SUCCESS)
{
SQLFreeStmt(hStmt, SQL_CLOSE);
return false;
}
SQLSMALLINT ColCount;
SQLNumResultCols(hStmt, &ColCount);
for(SQLSMALLINT i=1; i<=ColCount; i++)
{
char chColName[1024] = {0};
SQLSMALLINT nLen = 1022;
SQLColAttribute(hStmt, i, SQL_DESC_NAME, chColName, nLen, &nLen, NULL);
lstCol.push_back(chColName);
}
do{
for(SQLSMALLINT i=1; i<=ColCount; i++)
{
char chData[65536] = {0};
SQLINTEGER nLen = 65534;
SQLGetData(hStmt, i, SQL_C_CHAR, chData, nLen, &nLen);
lstData.push_back(chData);
}
sqlReturn = SQLFetch(hStmt);
}while(SQL_SUCCESS == sqlReturn);
SQLFreeStmt(hStmt, SQL_CLOSE);
return true;
}
[Oracle][ODBC][Ora]ORA-00604: error occurred at recursive SQL level 1
ORA-01000: maximum open cursors exceeded
ORA-00604: error occurred at recursive SQL level 1
ORA-01000: maximum open cursors exceeded
ORA-01000: maximum open cursors exceeded
接下来又搜索“ORA-00604”和“ORA-01000”错误码信息,ORA-00604表明在数据库内部执行SQL语句时发生了(递归)错误,一般发生此错误时,还伴随着其它的错误,也就是当前的ORA-01000(即超过了最大打开游标数);ORA-01000有两种说法,分别是表空间不足和打开的游标数量已达最大。
(1)先分析表空间:通过查看“表在哪个空间”及“表空间是否自增”发现,表是自增的,不存在表空间不足且dbf文件只有5M,分析过程如下。
查看表在哪个空间
|
查看表空间是否自增
|
(2)再分析游标数量:
默认游标数为300,于是在程序中增加nNum变量调试,发现SQLExecDirect返回-1的时候nNum的值刚好是300。
通过以上分析得知问题根本所在:游标打开数超限(Oracle游标每次查询的时候都会打开,并且查询结束后游标并不会关闭,这样会导致打开的游标越来越多,最终超出上限。),继而猜测是否SQLFreeStmt(SQLHSTMT, SQL_CLOSE)释放语句句柄时未释放游标,于是又去搜“SQLHSTMT怎么释放干净”,找到一篇文章(SQLFreeStmt( m_hstmt, SQL_CLOSE)),看到文末了解到,SQLFreeStmt函数的功能由fOption参数的取值决定,注意到SQL_CLOSE与SQL_DROP的区别,于是把执行sql的函数中的SQL_CLOSE全部替换为SQL_DROP,运行一段时间,Oracle读取数据也正常了。
分界线
由于语句句柄是函数局部的,一直需要Alloc分配新的语句句柄(影响性能),又将SQLHSTMT作为类的成员,只在连接/断开数据源的时候分配/释放句柄,于是代码改动如下:
class CDataBaseClient
{
SQLHSTMT m_hStmt;
};
bool CDataBaseClient::Connect(string strAddr,string strUser,string strPwd)
{
sqlReturn = SQLAllocStmt(m_hConnection, &m_hStmt);
if (sqlReturn != SQL_SUCCESS && sqlReturn != SQL_SUCCESS_WITH_INFO)
{
SQLFreeStmt(m_hStmt, SQL_DROP);
m_hStmt = NULL;
SQLFreeConnect(m_hConnection);
m_hConnection = NULL;
SQLFreeEnv(m_hEnviroment);
m_hEnviroment = NULL;
return false;
}
return true;
}
void CDataBaseClient::DisConnect()
{
if(m_hStmt)
{
SQLFreeStmt(m_hStmt, SQL_DROP);
m_hStmt = NULL;
}
}
bool CDataBaseClient::QuerySql(string strSql,list<string>&lstCol,list<string>&lstData)
{
SQLRETURN sqlReturn=SQLExecDirect(m_hStmt,(SQLCHAR*)strSql.data(), strSql.length());
if (sqlReturn != SQL_SUCCESS && sqlReturn != SQL_SUCCESS_WITH_INFO)
{
return false;
}
return true;
}
运行程序后发现,Oracle和MySQL读取正常,SQL Server读取成功一次后续就读不到数据了,查看strErrorMsg错误信息为"[Microsoft][ODBC SQL Server Driver]无效的游标状态",然后试想在执行sql函数结束前关闭(SQL_CLOSE)与语句句柄相关联的游标,代码如下:
bool CDataBaseClient::QuerySql(string strSql, list<string>&lstCol, list<string>&lstData)
{
SQLRETURN sqlReturn = SQLExecDirect(m_hStmt,(SQLCHAR*)strSql.data(), strSql.length());
if(sqlReturn != SQL_SUCCESS && sqlReturn != SQL_SUCCESS_WITH_INFO)
{
SQLFreeStmt(m_hStmt, SQL_CLOSE);
return false;
}
lstCol.clear();
lstData.clear();
if(SQLFetch(m_hStmt) != SQL_SUCCESS)
{
SQLFreeStmt(m_hStmt, SQL_CLOSE);
return false;
}
SQLFreeStmt(m_hStmt, SQL_CLOSE);
return true;
}
加上SQLFreeStmt(m_hStmt, SQL_CLOSE)之后,运行程序,三种数据库读取数据均正常了。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)