多线程使用unixODBC时,必须的配置(threadintg)

unixODBC是linux在平台上实现的ODBC驱动管理器(Driver Manager),当应用程序调用C函数SQLDriverConnect时,会依据参数以及ODBC的配置文件内容去调用具体的ODBC驱动程序动态库。比如MaxDB的驱动libsqlod.so和MySQL的MyODBC驱动libmyodbc3.so等。
在linux下,ODBC的配置文件有如下位置:
~/.odbc.ini
/etc/odbc.ini 或者 /usr/local/etc/odbc.ini
/etc/odbcinst.ini 或者 /usr/local/etc/odbcinst.ini
通常以rpm安装的unixODBC,是/etc下的配置文件起作用,如果是编译后安装的,是/usr/local/etc下的配置文件起作用;~/.odbc.ini是对该
用户的进程起作用,比如redhat系列的操作系统,以用户normal运行的进程,则会去找/home/normal/.odbc.ini。

配置文件中的大多数参数,在很多网站上都有说明,比如微软的网站、unixODBC的网站、MySQL的网站等等,这里就不多说了。本文只介绍一个unixODBC特有的,又与多线程有很大关系的参数----threading。从目前的经验来看,只要使用多线程来调用ODBC,用MySQL和MaxDB时是必须要设置的,通常设为0.
threading共有四个可取的值,从0到3.如果不设置此值的话,unixODBC会使用一个缺省值,比如我遇到的情况,缺省值是3。
在取1到3时,unixODBC会在调用ODBC函数时,加不同级别的锁,锁的粒度越粗,则各线程之间就越容易由于锁的互斥而进行等待。这种做法的初衷是考虑到具体的驱动可能不是线程安全的。
但是MaxDB的ODBC驱动,和MyODBC驱动,在各个线程不共享数据库连接的情况下,都是线程安全的。
并且从这一段时间的实践,如果不设置threading(即使用缺省值),一方面会导致速度很慢,另一方面会由于unixODBC加的锁与数据库端的锁共同作用而造成死锁。
比如有两个线程,分别称为A和B,它们都通过ODBC访问同一个数据库。
首先,A由于某个数据库操作,获得了数据库的某个锁,不妨称为DBLock;
然后,B调用某个ODBC函数,由unixODBC依据threading的值,通过pthread_mutex_lock获得一个锁,不妨称为UNLock;
之后,B恰巧由于某个操作,请求A获得的DBLock,此时在数据库一端就被锁住了,等待DBLock的释放,则B一直等待数据库的回应;
之后,A又要调用某个ODBC函数,而恰巧又要请求B已经获得的UNLock,这样,线程A就阻塞了,等待UNLock的释放。
也就是说,A在等B,B又在等A,客户端的锁与服务器端的锁共同作用,造成死锁。线程并未报错或者退出,但也不干活。
以目前的经验,在linux上使用ODBC连接MaxDB和MySQL,要在odbcinst.ini中加一句:
threading = 0

至于它的具体含义,我在网上查了挺久都没看到对它的详细解释,有点印象,以前好像在哪里见过的,但现在怎么也找不到了。最后在unixODBC的网站上发现这样一段话:
Since 1.6 if the driver manager was built with thread support you may add another entry to each driver entry. For example

[PostgreSQL]
Description = PostgreSQL driver for Linux & Win32
Driver = /usr/local/lib/libodbcpsql.so
Setup = /usr/local/lib/libodbcpsqlS.so
Threading = 2

This entry alters the default thread serialization level. More details can be found in the file DriverManager/__handles.c in
the source tree.

看unixODBC相关源码,首先是与threading取值相关的宏:
#if defined ( HAVE_LIBPTHREAD ) || defined ( HAVE_LIBTHREAD ) || defined ( HAVE_LIBPTH )
#define TS_LEVEL0 0 /* no implicit protection, only for */
/* dm internal structures */
#define TS_LEVEL1 1 /* protection on a statement level */
#define TS_LEVEL2 2 /* protection on a connection level */
#define TS_LEVEL3 3 /* protection on a environment level */
#endif
共有四个值,表示在什么级别上加锁。在unixODBC有这样一个函数,几乎在调每个ODBC函数的时候,都会调这个函数:
void thread_protect( int type, void *handle )
{
DMHENV environment;
DMHDBC connection;
DMHSTMT statement;
DMHDESC descriptor;

switch( type )
{
case SQL_HANDLE_ENV:
mutex_entry( &mutex_env );
break;

case SQL_HANDLE_DBC:
connection = handle;
if ( connection -> protection_level == TS_LEVEL3 )
{
mutex_entry( &mutex_env );
}
else if ( connection -> protection_level == TS_LEVEL2 ||
connection -> protection_level == TS_LEVEL1 )
{
mutex_entry( &connection -> mutex );
}
break;
case SQL_HANDLE_STMT:
statement = handle;
if ( statement -> connection -> protection_level == TS_LEVEL3 )
{
mutex_entry( &mutex_env );
}
else if ( statement -> connection -> protection_level == TS_LEVEL2 )
{
mutex_entry( &statement -> connection -> mutex );
}
else if ( statement -> connection -> protection_level == TS_LEVEL1 )
{
mutex_entry( &statement -> mutex );
}
break;

case SQL_HANDLE_DESC:
descriptor = handle;
if ( descriptor -> connection -> protection_level == TS_LEVEL3 )
{
mutex_entry( &mutex_env );
}
if ( descriptor -> connection -> protection_level == TS_LEVEL2 )
{
mutex_entry( &descriptor -> connection -> mutex );
}
if ( descriptor -> connection -> protection_level == TS_LEVEL1 )
{
mutex_entry( &descriptor -> mutex );
}
break;
}
}
这样基本上就有些概念了。
结论是,使用unixODBC时,一定要在odbcinst.ini中设置threading,通常设为0,避免用它的缺省值(很可能是3)。
[@more@]

你可能感兴趣的:(框架与设计,MySql,Linux/Unix)