通过ODBC在C中调用Postgres存储过程获取游标数据报错排查
解决Postgres存储过程游标在ODBC调用中不存在的问题
核心原因
PostgreSQL中,普通游标(未声明WITH HOLD)的生命周期绑定到当前事务。ODBC默认处于自动提交模式,执行CALL TEST_SP()后事务会立即提交,存储过程中创建的游标rs_resultone会被自动销毁,后续执行FETCH自然会提示游标不存在。而pgAdmin默认在未提交的事务会话中执行操作,所以游标能正常访问。
纯C代码解决方案
通过关闭ODBC连接的自动提交,让CALL和FETCH处于同一个事务上下文即可解决问题,无需修改存储过程。
完整代码示例
#include <sql.h> #include <sqlext.h> #include <stdio.h> void printODBCError(SQLHANDLE handle, SQLSMALLINT handleType) { SQLCHAR sqlState[6], msg[SQL_MAX_MESSAGE_LENGTH]; SQLINTEGER nativeError; SQLSMALLINT msgLen; while (SQLGetDiagRec(handleType, handle, 1, sqlState, &nativeError, msg, sizeof(msg), &msgLen) == SQL_SUCCESS) { printf("ODBC Error: %s (Native: %d) - %s\n", sqlState, nativeError, msg); } } int main() { SQLHENV henv; SQLHDBC hdbc; SQLHSTMT hstmt; SQLRETURN ret; // 初始化ODBC环境 ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv); ret = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0); // 分配连接句柄 ret = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc); // 连接数据库(替换为你的连接字符串) const char* connStr = "DSN=PostgresDSN;UID=your_user;PWD=your_pass;"; ret = SQLDriverConnect(hdbc, NULL, (SQLCHAR*)connStr, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE); if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { printODBCError(hdbc, SQL_HANDLE_DBC); return 1; } // 关闭自动提交,开启事务模式 ret = SQLSetConnectAttr(hdbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, 0); if (ret != SQL_SUCCESS) { printODBCError(hdbc, SQL_HANDLE_DBC); return 1; } // 分配语句句柄 ret = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt); if (ret != SQL_SUCCESS) { printODBCError(hdbc, SQL_HANDLE_DBC); return 1; } // 调用存储过程 const char* callSp = "CALL TEST_SP();"; ret = SQLExecDirect(hstmt, (SQLCHAR*)callSp, SQL_NTS); if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { printODBCError(hstmt, SQL_HANDLE_STMT); goto cleanup; } // 清除存储过程执行后的空结果集(如果存储过程无返回结果集,可省略) while (SQLMoreResults(hstmt) == SQL_SUCCESS); // 执行FETCH获取游标数据 const char* fetchSql = "FETCH ALL FROM rs_resultone;"; ret = SQLExecDirect(hstmt, (SQLCHAR*)fetchSql, SQL_NTS); if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { printODBCError(hstmt, SQL_HANDLE_STMT); goto cleanup; } // 处理游标结果集 SQLCHAR colValue[256]; SQLLEN colLen; printf("Cursor data:\n"); while ((ret = SQLFetch(hstmt)) == SQL_SUCCESS) { // 替换为你的列索引和数据类型 ret = SQLGetData(hstmt, 1, SQL_C_CHAR, colValue, sizeof(colValue), &colLen); if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) { printf("- %s\n", colValue); } } // 提交事务,释放游标 ret = SQLTransact(NULL, hdbc, SQL_COMMIT); if (ret != SQL_SUCCESS) { printODBCError(hdbc, SQL_HANDLE_DBC); } cleanup: // 释放资源 SQLFreeHandle(SQL_HANDLE_STMT, hstmt); SQLDisconnect(hdbc); SQLFreeHandle(SQL_HANDLE_DBC, hdbc); SQLFreeHandle(SQL_HANDLE_ENV, henv); return 0; }
关键注意点
- 关闭自动提交:这是解决问题的核心,确保
CALL和FETCH在同一个事务中执行,游标不会被提前销毁。 - 清除空结果集:存储过程执行后可能返回空结果集,需用
SQLMoreResults清除,否则后续SQLExecDirect会报错。 - 事务提交:处理完游标数据后必须提交事务,避免连接长期处于事务状态占用资源。
内容的提问来源于stack exchange,提问作者Radek Matras
相关产品推荐
相关产品推荐

