I have a C/C++ DLL that is connecting to SQL and issuing a large number of ODBC queries rapidly in a loop. The only thing is that it is turning out to be so much slower from the ODBC DLL than running the query from T-SQL in Management Studio. Many orders of magnitude slower.
At first I thought it might be the query itself, but then I stripped it down to a simple "select NULL" and still got the same results.
I was wondering if this is expected or whether there is some ODBC setting that I am missing or getting wrong?
First I connect like this (for brevity I have omitted all error checking, however, retcode is returning SQL_SUCCESS in all cases):
char *connString = "Driver={SQL Server};Server=.\\ENT2012;uid=myuser;pwd=mypwd";
...
retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_UINTEGER);
retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
SQLSetConnectAttr(hdbc, SQL_LOGIN_TIMEOUT, (void*)5, 0);
retcode = SQLDriverConnect(
hdbc,
0,
(SQLTCHAR*) connString,
SQL_NTS,
connStringOut,
MAX_PATH,
(SQLSMALLINT*)&connLen,
SQL_DRIVER_COMPLETE);
Then I prepare the statement, bind a parameter (unused in this example), and bind a column like this:
char queryString = "select NULL;";
SQLLEN g_int32 = 4;
SQLLEN bytesRead = 0;
...
retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt)
retcode = SQLPrepare(hstmt, queryString, SQL_NTS);
retcode = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT,
SQL_C_LONG, SQL_INTEGER, sizeof(int), 0, spid, 0, (SQLLEN*)&g_int32))
retcode = SQLBindCol(hstmt, 1, SQL_C_CHAR, col_1, 32, &bytesRead);
Finally, I repeatedly call the query (e.g., 10000 times) in a loop like this:
retcode = SQLExecute(hstmt);
retcode = SQLFetch(hstmt);
SQLCloseCursor(hstmt);
This takes about 90 seconds to run 10000 times in the ODBC DLL. Testing on a 4 core Windows 2008 R2 Server running SQL 2012 x64.
On the other hand, if I run, what looks to me to be, an equivalent test in Management Studio, it takes less than a second:
declare #sql varchar(128), #repeat int;
set #repeat = 10000;
set #sql = 'select NULL;';
while #repeat > 0 begin
exec(#sql);
set #repeat = #repeat - 1;
end;
Can someone point out something that I am overlooking? Some flaw in my logic?
Thanks.
Neil Weicher
www.netlib.com
This is too long for a comment
declare #sql varchar(128), #repeat int;
set #repeat = 10000;
set #sql = 'select NULL;';
while #repeat > 0 begin
exec(#sql);
set #repeat = #repeat - 1;
end;
does not realistically simulate the same as 10000 remote calls. exec bypasses a lot of the internals of setting up a request. To simulate 10000 calls do this in SSMS:
select NULL;
go 10000
and measure. Output as text probably should be used to avoid timing around SSMS grid display.
I'm not all that familiar with the T-SQL stuff but here are a couple things to consider.
Your ODBC driver has to transfer data via your network and I suspect the T-SQL execution does not. Next to disk IO, transfering data on the network is one of the slowest things your ODBC driver will have to do. You may find that the driver is spending considerable time either waiting on the data to travel to clearing data off the wire.
Also, it's not clear to me that your T-SQL example actually moves and data but your ODBC example does when SQLFetch is called. The T-SQL may just be executing the query and never fetching any data. So, removing SQLFetch from the loop might be a more equal comparison.
To see if data transfer is your limiting factor estimate how much data will be included in all the records you fetch with ODBC and try to move that much data between the two machines with something like FTP. An ODBC driver will never be able to fetch data faster than a simple raw transfer of data. I see you are just fetching NULL so not much in your result set but the driver and database still transfer data between them to service this request. Could be several hundred bytes per execution\fetch.
I faced the same issue. I solved it by changing the "Cursor Default Mode" setting of the DSN for the ODBC driver (through the ODBC Administrator tool) from "READ_ONLY" to "READ_ONLY_STREAMING". This alone increased the speed of my application (query data and write them to file) from 260 seconds to 51 seconds using Java 32-bit and from 1234 seconds to 11 seconds using C++.
See this post: http://www.etl-tools.com/forum/visual-importer/1587-question-about-data-transformation-memory-usage?start=6
Related
I have to perform some calculations with data stored in an MSSQL Server database and then save the results in the same database.
I need to load (part of) a table into C++ data structures, perform a calculation (that can take substantial time), and finally add some rows to the same table.
The problem is that several users can access the database concurrently, and I want the table to be locked since the data is loaded in memory until the results of the calculation are written to the table.
Using the ODBC SDK, is it possible to explicitly lock and unlock part of a table?
I have tried the following test program, but unfortunately the INSERT statement succeeds before StmtHandle1 is freed:
SQLDriverConnect(ConHandle1, NULL, (SQLCHAR *)"DRIVER={ODBC Driver 13 for SQL Server};"
"SERVER=MyServer;"
"DATABASE=MyDatabase;"/*, ... */);
SQLSetStmtAttr(StmtHandle1,SQL_ATTR_CONCURRENCY,(SQLPOINTER)SQL_CONCUR_LOCK,SQL_IS_INTEGER);
SQLExecDirect(StmtHandle1, (SQLCHAR *)"SELECT * FROM [MyTable] WITH (TABLOCKX, HOLDLOCK)", SQL_NTS);
SQLDriverConnect(ConHandle2, NULL, (SQLCHAR *)"DRIVER={ODBC Driver 13 for SQL Server};"
"SERVER=MyServer;"
"DATABASE=MyDatabase;"/*, ... */);
SQLSetStmtAttr(StmtHandle2,SQL_ATTR_CONCURRENCY,(SQLPOINTER)SQL_CONCUR_LOCK,SQL_IS_INTEGER);
SQLExecDirect(StmtHandle2, (SQLCHAR *)"INSERT INTO [MyTable] VALUES (...)", SQL_NTS);
unfortunately the INSERT statement succeeds before StmtHandle1 is
freed
By default SQL Server opereates in autocommit mode, i.e. opens a tarnsaction and commits it for you.
You requested TABLOCKX and the table was locked for the duration of your transaction, but what you want instead is to explicitely open a transaction and don't commit/rollback it until you'll done with your calculations, i.e. you should use
begin tran; SELECT top 1 * FROM [MyTable] WITH (TABLOCKX, HOLDLOCK);
And you don't need to read the whole table, top 1 * is sufficient.
I have a windows application that does a bulk copy of data to an SQL table, using the bcp_xxx() functions and a standard "SQL Server" ODBC connector.
I want to move to supporting native SQL client connections
According to what I have seen, all I need to do is add
#define _SQLNCLI_ODBC_
#include <sqlncli.h>
to the top of the cpp file, and link against "sqlncli10.lib" rather than "odbcbcp.lib."
However, when I call bcp_bind(), I get "Access violation reading location 0xffffffffffffffff"
This happens with both "SQL server" and "SQL Native Client" ODBC connections.
The code being executed is:
SQLHANDLE hEnv;
SQLHANDLE hConnection;
LPCBYTE data = (LPCBYTE)malloc(100);
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);
SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER);
SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hConnection);
SQLSetConnectAttr(hConnection, SQL_COPT_SS_BCP, (void *)SQL_BCP_ON, SQL_IS_INTEGER) ;
SQLConnect(hConnection, "DSN", SQL_NTSL, NULL, 0, NULL, 0);
bcp_init(hConnection, "DB_TABLE", NULL, NULL, DB_IN);
bcp_bind(hConnection, data, 0, SQL_VARLEN_DATA, (LPCBYTE)"", 1, SQLCHARACTER, 1);
The problem appears to be due to using "SQL server" ODBC connections at the same time as the bulk copy routines are trying to use native connections.
If I change all other ODBC connections to be SQL Native, the bulk copy routines work correctly.
Where exactly sqlite3 database will be at SQLITE_BUSY state for other threads and processes. (db at default mode SERIALIZE, not WAL)
Simple Example to Illustrate the question:
char buffer[] = "SELECT sessionid FROM sessions WHERE something < 1000";
sqlite3_prepare_v2(db, buffer, strlen(buffer), &stmt, 0)
// IS DB SQLITE_BUSY HERE ? PLACE 1
while( sqlite3_step(stmt) == SQLITE_ROW )
{
// IS DB SQLITE_BUSY HERE ? PLACE 2
}
// IS DB STILL SQLITE_BUSY HERE? PLACE 3
sqlite3_finalize(stmt);
I know for a fact that both sqlite3_prepare_v2 and sqlite3_step can error with SQLITE_BUSY (the docs say so and I've encountered it many times). The docs are less clear for sqlite3_finalize but my impression is that sqlite3_finalize is merely for memory management, so it should not do any database access.
sqlite3_step is the most likely place for this to happen, since it is what actually performs things like "INSERT INTO..." and "COMMIT" which tend to be heavy on the database.
SQLite is not terribly helpful when it comes to concurrency. By default, it does not even provide any fairness guarantees (although you can write them yourself as long as your concurrency is happening within the same process).
I have a DB2 database in my CentOS 6.5 64 bit machine and have installed ODBC drivers to access the database from my C++ application.
I am using SQL CLI APIs to fetch the data from the database.
If I fetch the data from my main() function of the C++ application, SQL CLI APIs work fine (e.g. SQLAllocHandle) and I am able to read/write data from the database.
If I try to do the same operation on my POSIX thread, SQL CLI APIs fail to initialize the handle and eventually read/write fails.
SQLRETURN sqlrc = SQL_SUCCESS;
SQLCHAR pszSqlState[100];
SQLINTEGER pfNativeError[100];
SQLCHAR pszErrorMsg[100];
SQLSMALLINT cbErrorMsgMax;
SQLSMALLINT pcbErrorMsg;
/* allocate an environment handle */
sqlrc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_hEnv);
if (sqlrc != SQL_SUCCESS)
{
return 1;
}
sqlrc = SQLGetDiagRec(SQL_HANDLE_ENV, m_hEnv, 1, pszSqlState, pfNativeError, pszErrorMsg, 100, &pcbErrorMsg);
/* set attribute to enable application to run as ODBC 3.0 application */
sqlrc = SQLSetEnvAttr(m_hEnv,
SQL_ATTR_ODBC_VERSION,
(void*)SQL_OV_ODBC3,
0);
/* allocate a database connection handle */
sqlrc = SQLAllocHandle(SQL_HANDLE_DBC, m_hEnv, &m_hDBconn);
sqlrc = SQLGetDiagRec(SQL_HANDLE_DBC, m_hEnv, 1, pszSqlState, pfNativeError, pszErrorMsg, 100, &pcbErrorMsg);
/* connect to the database */
sqlrc = SQLConnect(m_hDBconn,
(SQLCHAR *)db1Alias, SQL_NTS,
(SQLCHAR *)user, SQL_NTS,
(SQLCHAR *)pswd, SQL_NTS);
//sqlrc = SQLGetDiagRec(SQL_HANDLE_DBC, m_hDBconn, 1, pszSqlState, pfNativeError, pszErrorMsg, 100, &pcbErrorMsg);
return sqlrc;
I am using Eclipse / IBM Data Studio for development.
I googled for known issues without any luck.
How to access a DB2 database with ODBC from a separate thread?
Info :
The first APi
SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_hEnv )
returns SUCCESS but the value of m_hEnv is invalid (-ve value).
So the subsequent APIs return -2 which is SQL_INVALID_HANDLE .
Call to
SQLSetEnvAttr(m_hEnv,SQL_ATTR_ODBC_VERSION,(void *)SQL_OV_ODBC3, 0);
returns -2 SQL_INVALID_HANDLE .
The problem was, my application which uses SQL CLI code was not compiled using -D_REENTRANT flag.
Now I am able to use the SQL APIs inside my threads. Thanks all for your inputs.
I'm writing my first database application following a sample program the teacher given, but neither the sample, nor my own program can't connect to the database. (The JDBC sample program can, so the server should be OK).
I have these vars in the class declaration:
SQLHENV env;
SQLHDBC dbc;
SQLHSTMT stmt;
SQLRETURN ret;
Here's the constructor of my database handler class, that's where the connection should be made:
DBModule::DBModule(string server, string database)
{
this->server = server; //"localhost" is loaded into it
this->database = database; //"test" is loaded into it, of course it exists on the server
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
command = "DRIVER={MySQL ODBC 3.51 Driver};SERVER="+this->server+";DATABASE="+this->database+";";
//command looks like this now:
//"DRIVER={MySQL ODBC 3.51 Driver};SERVER=localhost;DATABASE=test;"
ret = SQLDriverConnect(dbc, NULL, (SQLWCHAR *)command.c_str(), SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);
if (!SQL_SUCCEEDED(ret)) {
err += CONNECT_DATABASE*DATABASE_UNREACHABLE;
good = false;
return;
} else {
good = true;
}
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
command = sysToStd(DBINIT);
SQLPrepare(stmt, (SQLWCHAR *)command.c_str(), SQL_NTS);
ret = SQLExecute(stmt);
}
The ret at SQLDriverConnect gets a -1 value.
I'm using the latest XAMPP as server with all the default settings (so i'm "root" and there is no password). I've tried adding UID=root to the connection string, but it did the same.
Thanks for any help.
You probably do not have MySQL ODBC drivers installed. JDBC works because you need not "install" them: they are some .jar files that can come with Java application. If you will use ODBC then install MySQL ODBC drivers, configure connection in ODBC Manager as System DSN, then from ODBC manager check if it connects to database (most ODBC drivers I know have "test connection" button).
When such test shows you "connected" or similar, then you can test if your application connects. Your connect sting looks like:
DRIVER={MySQL ODBC 3.51 Driver};SERVER=...;DATABASE....
so according to: http://www.connectionstrings.com/mysql#p30 it looks you are trying to use MySQL Connector/ODBC 3.51
Maybe database is not listening on dafult port?