PostgreSQL ODBC 查询表数据,使用游标分批返回数据

通过 ODBC 连接 PostgreSQL 数据库查询表数据,如果表中数据量非常大,一次性全部取出会占用大量的内存,可能导致程序崩溃。此时可以考虑采用游标分批返回数据的方式来实现。

ODBC 使用游标分批返回数据需要先在 odbc.ini 配置文件中配置参数 UseDeclareFetch 和 Fetch。

  • UseDeclareFetch,表示启用数据库服务器的游标分批返回数据
  • Fetch,表示使用数据库服务器的游标时,结果集在客户端缓存的最大行数,默认值为 100

配置 odbc.ini,如下:

[pg]
Description = PostgreSQL ODBC
Driver = PostgreSQL
Database = postgres
Servername = 127.0.0.1
UserName = admin
Password = 123456
Port = 36099
ReadOnly = -1
ConnSettings = set client_encoding to UTF8
UseDeclareFetch = 1
Fetch = 10000

测试程序代码,查询表 t,如下:

#include <stdio.h>
#include <stdlib.h>
#include <sql.h>
#include <sqlext.h>
#include <string.h>

#define NAME_LEN 64
#define STMT_LEN 128

#define CHECK_ERROR(e, s, h, t) ({\
            if (e!=SQL_SUCCESS && e != SQL_SUCCESS_WITH_INFO) {extract_error(s, h, t); goto exit;} \
})

void extract_error(char *fn, SQLHANDLE handle, SQLSMALLINT type)
{
    SQLINTEGER i = 0;
    SQLINTEGER NativeError;
    SQLCHAR SQLState[ 7 ];
    SQLCHAR MessageText[256];
    SQLSMALLINT TextLength;
    SQLRETURN ret;

    fprintf(stderr, "\nThe driver reported the following error %s\n", fn);
    do
    {
        ret = SQLGetDiagRec(type, handle, ++i, SQLState, &NativeError,
                            MessageText, sizeof(MessageText), &TextLength);
        if (SQL_SUCCEEDED(ret)) {
            printf("%s:%ld:%ld:%s\n",
                        SQLState, (long) i, (long) NativeError, MessageText);
        }
    }
    while( ret == SQL_SUCCESS );
}

int main () {

        SQLHENV  henv  = SQL_NULL_HENV;         // Environment
        SQLHDBC  hdbc  = SQL_NULL_HDBC;         // Connection handle
        SQLHSTMT hstmt = SQL_NULL_HSTMT;        // Statement handle
        SQLRETURN retcode;
        long len;
        char buf[1024];
        char stmt[] = "select * from t";

        // Allocate an environment handle
        retcode=SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
        CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_ENV)",
                    henv, SQL_HANDLE_ENV);

        // We want ODBC 3 support
        retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
                            (void *) SQL_OV_ODBC3, 0);
        CHECK_ERROR(retcode, "SQLSetEnvAttr(SQL_ATTR_ODBC_VERSION)",
                    hdbc, SQL_HANDLE_DBC);

        // Allocate a connection handle
        retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
        CHECK_ERROR(retcode, "SQLAllocHandle", hdbc, SQL_HANDLE_DBC);

        // Connect to the DSN
        retcode=SQLDriverConnect(hdbc, NULL, "DSN=pg;", SQL_NTS,
                             NULL, 0, NULL, SQL_DRIVER_COMPLETE);
        CHECK_ERROR(retcode, "SQLDriverConnect(DSN=DATASOURCE;)",
                    hdbc, SQL_HANDLE_DBC);

        // Allocate a statement handle
        retcode=SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
        CHECK_ERROR(retcode, "SQLAllocHandle(STMT)", hstmt, SQL_HANDLE_STMT);


        retcode = SQLExecDirect(hstmt, stmt, SQL_NTS);

        if ( SQL_SUCCEEDED( retcode ))
        {
                while(SQL_SUCCEEDED(retcode = SQLFetch(hstmt)))
                {
                        SQLGetData(hstmt,1,SQL_C_CHAR, buf, 50, &len);
                        printf("%s\n", buf);

                }
        }
         else
        {
                CHECK_ERROR(retcode, "SQLExecDirect(STMT)", hstmt, SQL_HANDLE_STMT);
        }

exit:

    printf ("\nComplete.\n");

    // Free handles
    // Statement
    if (hstmt != SQL_NULL_HSTMT)
        SQLFreeHandle(SQL_HANDLE_STMT, hstmt);

    // Connection
    if (hdbc != SQL_NULL_HDBC) {
        SQLDisconnect(hdbc);
        SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
    }

    // Environment
    if (henv != SQL_NULL_HENV)
        SQLFreeHandle(SQL_HANDLE_ENV, henv);

    return 0;
}

编译执行,如下:

[zhang@localhost odbc]$ gcc select.c -o select -I/data/zhang/app/unixODBC/include -L/data/zhang/app/unixODBC/lib -lodbc
[zhang@localhost odbc]$ ./select
aaaaa
bbbbb
ccccc
ddddd
eeeee

Complete.

设置 PostgreSQL 的参数 log_statement = 'all',记录所有执行的 SQL,会发现如下的日志记录:

BEGIN;
declare "SQL_CUR0x2f92b5d0" cursor with hold for select * from t;
fetch 10000 in "SQL_CUR0xa0905d0";
close "SQL_CUR0xa0905d0";
commit;

文章评论

0条评论