“北漂”小记

2008年1月,我的学生时代画上了句号。离开校园,我成了“北漂”大军中的一员。

进入社会,第一个面临的事情就是租房。由于以前没有租房经验,所以第一次我就租了个一楼北向的一间卧室。开始有暖气还好,屋里不是很冷。后来等停了供暖,才感觉不妙。因为是一楼,还是朝北的,所以一天根本进不了多少阳光,屋里又潮湿又阴冷。没过多长时间,我就生了病。所以住了不到两个月,我就搬家了。

我的第二个住处搬到了南沙滩,这里介于北京四环和五环之间。据说在北京的中轴线上,风水很好。我在这里住了两年,也的确住的很舒服。这里离我的母校科技大学很近,夏天的时候吃完晚饭,我经常一个人走回校园,漫步在操场上,回忆着学生时代的往事。冬天的时候,我一边听着袁阔成先生的<<三国演义>>评书,一边在社区里散步。这些情景至今我还历历在目,对我来说是很美好的回忆。

2010年我换了工作,新公司的办公地点在西单。每天下班后,我就穿梭在西单附近的小胡同里,寻找晚饭的落脚点。现在回想起来,印象比较深的有一家清真饭馆,他们家做的拉条子味道不错。老板可能是青海人,因为餐厅的电视总是锁定在青海卫视频道。我经常是烤着火炉,喝着茶水,吃着拉条子,看着电视。这个场景现在回忆起来也是别有一番情调。

2011年2月18日,我和女友正式登记结婚了。我们在北三环租了一间一居室的房子。尽管房子不是自己的,我们收入也不高,但是漂泊了这么长时间,我终于有了家的感觉。每天下班回来,两个人围在一起吃晚饭的感觉很是让我温馨,也很让我满足。

一眨眼,我成为“北漂”已经快5年了。尽管每天生活有太多的不如意,我还是尝试着让自己内心平和地去接受发生的一切。因为无论发生什么,生活总要继续,我还要向前走。就像人们常说的:做好自己的份内事,然后去等待,看看会发生什么……

unixODBC使用经验分享

unixODBC(http://www.unixodbc.org)和iODBC(http://www.iodbc.org)是UNIX系统上两个开源的ODBC实现。关于二者的比较,有兴趣的朋友可以参考stackoverflow上的这篇帖子。我没有用过iODBC,所以不好评论孰优孰劣。但是从stackoverflow上关于两个项目的标签统计来看,应该是unixODBC用的更广泛一些。在距离上一版本发布两年以后,今年10月,unixODBC发布了2.3.2版本,基本上就是修正了一些bugs(其中有3个bug是我发现并提出修改意见的),没有新的feature引入。我从2011年开始在项目中使用unixODBC,2012年中旬项目正式上线,到现在已经运行了1年半时间。总的来说unixODBC还是很稳定的,基本连个错误都没有。在这篇文章中,我把我使用unixODBC的一点经验分享出来,希望能给需要的朋友一些帮助。

(1)源码,编译和安装
在unixODBC的网站上提供了最新版本的源码下载,如果想下载之前的发布版本,可以访问这个网站:ftp://ftp.unixodbc.org/pub/unixODBC/。个人感觉,unixODBC的代码风格还是很清晰的,前后也比较一致(unixODBC里面也包含了一些其它开源的代码,像libltdl,而这些开源代码的风格和unixODBC的风格是不一致的)。只是有些函数有些太长了(像SQLConnect函数就有600多行),似乎不符合KISS(Keep It Small and Simple)原则。
编译unixODBC很简单:

./configure;
make;
make install

我的很多同事都说在Solaris环境下编译源代码是很痛苦的事情,但是编译unixODBC却总是一气呵成,连个错误都没有。
此外,由于默认编译unixODBC是优化的,所以我在实验室里使用时一般都是禁止优化的(./configure CFLAGS=’-g -O0 ‘),这样debug时也比较方便,不会出现代码和函数对不上的问题。只有到了生产环境,才把优化打开。

(2)使用连接池
从2.0.0版本起,uinixODBC引入了连接池。unixODBC的项目负责人Nick Gorham在这篇文章里详细介绍了连接池。引入连接池,可以使对数据库连接的使用更高效。下面我就对连接池的原理做个分析:
首先,uinixODBC引入两个全局变量:

/*
 * connection pooling stuff
 */

CPOOL *pool_head = NULL;
int pooling_enabled = 0;

其中pool_head指向连接池链表的首节点,而pooling_enabled则表示是否使用连接池。
其次,在SQLDisconnect函数中,可以看到:

/*
 * is it a pooled connection, or can it go back 
 */

if ( connection -> pooled_connection )
{
    __clean_stmt_from_dbc( connection );
    __clean_desc_from_dbc( connection );

    return_to_pool( connection );

    ......

    return function_return( SQL_HANDLE_DBC, connection, SQL_SUCCESS );
}
else if ( pooling_enabled && connection -> pooling_timeout > 0 ) 
{
    __clean_stmt_from_dbc( connection );
    __clean_desc_from_dbc( connection );

    return_to_pool( connection );

    ......

    return function_return( SQL_HANDLE_DBC, connection, SQL_SUCCESS );
}

如果使用了连接池的话,当断开连接时,连接是回到了连接池里,也就是return_to_pool这个函数。
最后,在SQLConnect函数中,可以看到:

if ( pooling_enabled && search_for_pool( connection, 
                                            server_name, name_length1,
                                            user_name, name_length2,
                                            authentication, name_length3,
                                            NULL, 0 ))
{
    ret_from_connect = SQL_SUCCESS;

    .....

    connection -> state = STATE_C4;

    return function_return( SQL_HANDLE_DBC, connection, ret_from_connect );
}

如果连接数据库时可以在连接池中找到可用的连接(search_for_pool),函数就直接返回,不用再做连接的操作了。

(3)在日志中打印线程
unixODBC的日志输出是通过dm_log_write函数:

if ( !log_info.program_name )
{
    uo_fprintf( fp, "[ODBC][%s]%s[%s][%d]%s\n", __get_pid((SQLCHAR*) tmp ), 
            tstamp_str,
            function_name, line, message );
}
else
{
    uo_fprintf( fp, "[%s][%s]%s[%s][%d]%s\n", log_info.program_name,
        __get_pid((SQLCHAR*) tmp ), 
        tstamp_str,
        function_name, line, message );
}

可以看到,日志输出包含进程名,时间戳,函数名,行号,和消息。但是现在的程序基本是多线程的,所以当有多个线程同时访问数据库时,从日志就很难区分开哪个线程到底做了什么,因此通常我会在日志中增加对线程的打印。因为我们的程序运行在Solaris系统,而在Solaris上,pthread_t类型是个整数,标示当前的线程号,所以下面的改动就可以增加对线程号的输出:

if ( !log_info.program_name )
{
    uo_fprintf( fp, "[ODBC][%s][%d]%s[%s][%d]%s\n", __get_pid((SQLCHAR*) tmp ), pthread_self(),
            tstamp_str,
            function_name, line, message );
}
else
{
    uo_fprintf( fp, "[%s][%s][%d]%s[%s][%d]%s\n", log_info.program_name,
        __get_pid((SQLCHAR*) tmp ), 
        pthread_self(),
        tstamp_str,
        function_name, line, message );
}

由于pthread_t类型在不同的系统定义不一样,所以如果同一份unixODBC代码需要运行在不同系统上,就需要实现定制化:

#ifdef __linux__
......
#endif

#ifdef __sun
......
#endif

关于pthread_t的打印,可以参考stackoverflow上的这篇帖子
以上就是我使用unixODBC的一点经验分享,希望能给需要的朋友一点帮助。如果大家有其它好的经验,也希望能分享出来。