unixODBC Programming - improve performance using multiple statements

 ODBC programming generally has the following structure.


And the SQL query statement is processed using the statement handle.
However, if there are many sql statements to be processed, the above structure should use one statement handle to process the work sequentially. However, RDBMS such as MySQL, MariaDB, MS-SQL, and DB2 are parallel structures that process multiple commands at the same time. Therefore, it is advantageous to create multiple statement handles and process them simultaneously.

<parallel processing>


Determining whether the RDBMS supports parallelism

ODBC SDK provides an API to know the number of statement handles that RDBMS can support simultaneously. You can use the SQLGetInfo function.

    SQLUSMALLINT    max_concur_act = -1;
    rc1 = SQLGetInfo(dbc,  SQL_MAX_CONCURRENT_ACTIVITIES , &max_concur_act, 0,0);
    if (SQL_SUCCEEDED(rc1)) {
        if(0 == max_concur_act) printf("SQL_MAX_CONCURRENT unlimited or undefined\n");
        else printf("SQL_MAX_CONCURRENT:%d\n",max_concur_act);
    }     

If the return value is 0, there is usually no special limit. Returns 0 for MySQL and MariaDB. If you are using another RDBMS, you can check it yourself using this function.


performance test


Single statement

The following is a test code that repeats 64,000 query statements using one statement.

#include <stdio.h>
#include <string.h>
#include <string>
#include <iostream>
#include <sql.h>
#include <sqlext.h>
#include <time.h>

using namespace std;

void debug_err(SQLRETURN rc, SQLHSTMT stmt){
    SQLLEN numRecs = 0;
    SQLSMALLINT   i, MsgLen;
    SQLRETURN     rc2; 
    SQLCHAR       SqlState[6], Msg[SQL_MAX_MESSAGE_LENGTH];
    SQLINTEGER    NativeError;  
    
    SQLGetDiagField(SQL_HANDLE_STMT, stmt, 0, SQL_DIAG_NUMBER, &numRecs, 0, 0);
    i = 1;  
    while (i <= numRecs && (rc2 = SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i, SqlState, &NativeError,  
            Msg, sizeof(Msg), &MsgLen)) != SQL_NO_DATA) {  
        printf("SQLSTATE[%s] NATIVE ERROR[%d] Msg[%s]\n",SqlState,NativeError,Msg);
        i++;  
    }         
}

int main( int argc, char** argv ) {
    SQLHENV env;
    SQLHDBC dbc;
    SQLHSTMT stmt;
    SQLRETURN ret; /* ODBC API return status */
    SQLSMALLINT columns; /* number of columns in result-set */
    int row = 0;
    char sql[1024] = "";
    SQLRETURN rc1;
    string conn("DSN=myodbc");

    /* Allocate an environment handle */
    SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
    /* We want ODBC 3 support */
    SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
    /* Allocate a connection handle */
    SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);

    rc1 = SQLDriverConnect(dbc, NULL, (SQLCHAR*)conn.c_str(), SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);
    if(SQL_SUCCESS != rc1){
        printf("SQLDriverConnect return [%d]\n", rc1);
    }
    SQLUSMALLINT    max_concur_act = -1;
    rc1 = SQLGetInfo(dbc,  SQL_MAX_CONCURRENT_ACTIVITIES , &max_concur_act, 0,0);
    if (SQL_SUCCEEDED(rc1)) {
        if(0 == max_concur_act) printf("SQL_MAX_CONCURRENT unlimited or undefined\n");
        else printf("SQL_MAX_CONCURRENT:%d\n",max_concur_act);
    }     
    
    /* Allocate a statement handle */
    SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
    /* execute a query */
    sprintf(sql, "select belong, phone from professor");
    int count = 64 * 1000;

    double diff;
    struct tm file_tm{};
    time_t now, end;
    time(&now);
    
    for(int x = 0; x < count; x++){
        rc1 = SQLExecDirect(stmt, (SQLCHAR*)sql, SQL_NTS);
        if(SQL_SUCCESS != rc1 && SQL_SUCCESS_WITH_INFO != rc1){
            debug_err(rc1, stmt);
            exit(0);
        }
        char belong[16], phone[16];
        SQLBindCol(stmt, 1, SQL_C_CHAR,  belong, 16, NULL);
        SQLBindCol(stmt, 2, SQL_C_CHAR,  phone, 16, NULL);


        for ( int i = 0; ; i++ ) {
            rc1 = SQLFetch( stmt );
            if ( rc1 == SQL_ERROR || rc1 != SQL_SUCCESS_WITH_INFO )
                debug_err(rc1, stmt);
            if ( rc1 == SQL_SUCCESS || rc1 == SQL_SUCCESS_WITH_INFO ) {
                cout << "Record [" << i + 1 << "]  ";
                cout << " belong : " << belong;
                cout << " \tphone : " << phone;
                cout << "\n";
            }
            else
                break;
        }
    }
    time(&end);
   
    rc1 = SQLFreeHandle( SQL_HANDLE_STMT, stmt );
    if(rc1 != SQL_SUCCESS){
        cout << "SQLFreeHandle (SQL_HANDLE_STMT)err:"<< rc1 << endl;
        exit(0);
    } 
    rc1 = SQLDisconnect( dbc );
    if(rc1 != SQL_SUCCESS){
        cout << "SQLDisconnect err:"<< rc1 << endl;
        exit(0);
    } 
    rc1 = SQLFreeHandle( SQL_HANDLE_DBC, dbc );
    if(rc1 != SQL_SUCCESS){
        cout << "SQLFreeHandle (SQL_HANDLE_DBC)err:"<< rc1 << endl;
        exit(0);
    } 
    rc1 = SQLFreeHandle( SQL_HANDLE_ENV, env );    
    if(rc1 != SQL_SUCCESS){
        cout << "SQLFreeHandle (SQL_HANDLE_ENV)err:"<< rc1 << endl;
        exit(0);
    } 
    cout << "basic odbc test end" << endl;    
    diff = difftime(end, now);
    cout <<"Elapsed time:" <<  diff <<endl;
    return 0;
}

<basic_perf.cpp>

Let's compile and run the program.

[root@localhost odbc]# g++ basic_perf.cpp -lodbc -o basic_perf
[root@localhost odbc]# ./basic_perf
......
......
......
Record [1]   belong : IDE       phone : 01112345678
Record [2]   belong : MSE       phone : 01121342443
Record [3]   belong : ESE       phone : 01123424343
Record [4]   belong : IME       phone : 08200003333
Record [5]   belong : NBA       phone : 01023452222
Record [6]   belong : IME       phone : 01134343222
Record [7]   belong : IDE       phone : 01123432432
basic odbc test end
Elapsed time:38

It took about 38 seconds to process 64000 queries.

This time, we will create multiple statement handles for parallel processing.


Multiple statements

This time, we will create 64 statement handles for 64000 query statements and process them simultaneously in 64 threads.

#include <stdio.h>
#include <string.h>
#include <string>
#include <iostream>
#include <sql.h>
#include <sqlext.h>
#include <time.h>
#include <pthread.h>

using namespace std;

SQLHDBC dbc;

void debug_err(SQLRETURN rc, SQLHSTMT stmt){
    SQLLEN numRecs = 0;
    SQLSMALLINT   i, MsgLen;
    SQLRETURN     rc2; 
    SQLCHAR       SqlState[6], Msg[SQL_MAX_MESSAGE_LENGTH];
    SQLINTEGER    NativeError;  
    
    SQLGetDiagField(SQL_HANDLE_STMT, stmt, 0, SQL_DIAG_NUMBER, &numRecs, 0, 0);
    i = 1;  
    while (i <= numRecs && (rc2 = SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i, SqlState, &NativeError,  
            Msg, sizeof(Msg), &MsgLen)) != SQL_NO_DATA) {  
        printf("SQLSTATE[%s] NATIVE ERROR[%d] Msg[%s]\n",SqlState,NativeError,Msg);
        i++;  
    }         
}

void *statement_thread(void *data)
{
    char sql[1024] = "";
    SQLRETURN rc1;

    SQLHSTMT stmt;

    /* Allocate a statement handle */
    SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
    /* execute a query */
    sprintf(sql, "select belong, phone from professor");
    for(int x = 0; x < 100; x++){
        rc1 = SQLExecDirect(stmt, (SQLCHAR*)sql, SQL_NTS);
        if(SQL_SUCCESS != rc1 && SQL_SUCCESS_WITH_INFO != rc1){
            debug_err(rc1, stmt);
            exit(0);
        }
        char belong[16], phone[16];
        SQLBindCol(stmt, 1, SQL_C_CHAR,  belong, 16, NULL);
        SQLBindCol(stmt, 2, SQL_C_CHAR,  phone, 16, NULL);


        for ( int i = 0; ; i++ ) {
            rc1 = SQLFetch( stmt );
            if ( rc1 == SQL_ERROR || rc1 != SQL_SUCCESS_WITH_INFO )
                debug_err(rc1, stmt);
            if ( rc1 == SQL_SUCCESS || rc1 == SQL_SUCCESS_WITH_INFO ) {
                cout << "Record [" << i + 1 << "]  ";
                cout << " belong : " << belong;
                cout << " \tphone : " << phone;
                cout << "\n";
            }
            else
                break;
        }
    }

    rc1 = SQLFreeHandle( SQL_HANDLE_STMT, stmt );
    if(rc1 != SQL_SUCCESS){
        cout << "SQLFreeHandle (SQL_HANDLE_STMT)err:"<< rc1 << endl;
    } 
    return NULL;
}


int main( int argc, char** argv ) {
    SQLHENV env;
    SQLRETURN ret; /* ODBC API return status */
    SQLSMALLINT columns; /* number of columns in result-set */
    int row = 0;
    SQLRETURN rc1;
    string conn("DSN=myodbc");

    /* Allocate an environment handle */
    SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
    /* We want ODBC 3 support */
    SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
    /* Allocate a connection handle */
    SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);

    rc1 = SQLDriverConnect(dbc, NULL, (SQLCHAR*)conn.c_str(), SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);
    if(SQL_SUCCESS != rc1){
        printf("SQLDriverConnect return [%d]\n", rc1);
    }
    SQLUSMALLINT    max_concur_act = -1;
    rc1 = SQLGetInfo(dbc,  SQL_MAX_CONCURRENT_ACTIVITIES , &max_concur_act, 0,0);
    if (SQL_SUCCEEDED(rc1)) {
        if(0 == max_concur_act) printf("SQL_MAX_CONCURRENT unlimited or undefined\n");
        else printf("SQL_MAX_CONCURRENT:%d\n",max_concur_act);
    }     
    
    int count = 64 * 1000;

    double diff;
    struct tm file_tm{};
    time_t now, end;
    time(&now);
    
    pthread_t p_thread[64];
    for(int x = 0; x < 64; x++){
        pthread_create(&p_thread[x], NULL, statement_thread, NULL);
    }
    
    for(int x = 0; x < 64; x++){
        pthread_join(p_thread[x], NULL);
    }
    
    time(&end);
   
    rc1 = SQLDisconnect( dbc );
    if(rc1 != SQL_SUCCESS){
        cout << "SQLDisconnect err:"<< rc1 << endl;
        exit(0);
    } 
    rc1 = SQLFreeHandle( SQL_HANDLE_DBC, dbc );
    if(rc1 != SQL_SUCCESS){
        cout << "SQLFreeHandle (SQL_HANDLE_DBC)err:"<< rc1 << endl;
        exit(0);
    } 
    rc1 = SQLFreeHandle( SQL_HANDLE_ENV, env );    
    if(rc1 != SQL_SUCCESS){
        cout << "SQLFreeHandle (SQL_HANDLE_ENV)err:"<< rc1 << endl;
        exit(0);
    } 
    cout << "basic odbc test end" << endl;    
    diff = difftime(end, now);
    cout <<"Elapsed time:" <<  diff <<endl;
    return 0;
}

<multi_perf.cpp>


Let's compile and run the program.

[root@localhost odbc]# g++ multi_perf.cpp -lodbc -o multi_perf
[root@localhost odbc]# ./multi_perf
......
......
......
Record [1]   belong : IDE       phone : 01112345678
Record [2]   belong : MSE       phone : 01121342443
Record [3]   belong : ESE       phone : 01123424343
Record [4]   belong : IME       phone : 08200003333
Record [5]   belong : NBA       phone : 01023452222
Record [6]   belong : IME       phone : 01134343222
Record [7]   belong : IDE       phone : 01123432432
basic odbc test end
Elapsed time:3

It took about 3 seconds to process 64000 queries. A speed improvement of more than 10 times was achieved.

The speed improvement could change more dramatically with the following factors:

  • The number of CPU cores, memories on the server on which the RDBMS is running.
  • network bandwidth
  • The number of CPU cores, memories of the client PC that executes the query statement


Wrapping up

If you need to execute a large number of queries at the same time, you can get much better performance by creating several statements at the same time and then executing the code using multi-threading.

You can download source codes at C-Python-Cooking .


댓글

이 블로그의 인기 게시물

MQTT - C/C++ Client

RabbitMQ - C++ Client #1 : Installing C/C++ Libraries

C/C++ - Everything about time, date