webpy+DBUtils是如何将数据库连接数占满的

妙音 posted @ 2014年1月10日 11:41 in webpy , 2483 阅读
webpy简单易用,而且非常轻量。这些特点很容易让人忽略它作为web服务器的主要目标,而把它当作一个小模块使用。
现在就把它当作小模块,只使用它操作数据库的api.
 
下面是个小程序,目的就是访问数据库,然后关闭连接。
import web

def main():
    db = web.database(port=5432, host='localhost', dbn='postgres', db='tax',
                      user='postgres', pw='postgres')
    rs = db.query("select count(*) as count from pg_stat_activity")
    print list(rs)[0].count
    db.ctx.db.close()

if __name__ == '__main__':
    main()
在正常情况下,上面程序表现很正常。不会有任何问题.
 
如果变成周期程序,并且安装了DBUtils模块
import time
import web
import psycopg2

def main():
    db = web.database(port=5432, host='localhost', dbn='postgres', db='tax',
                      user='postgres', pw='postgres')
    rs = db.query("select count(*) as count from pg_stat_activity")
    print list(rs)[0].count
    db.ctx.db.close()

if __name__ == '__main__':
    while True:
        main()
        time.sleep(1)
输出的count值不断增加,最终将连接数占满。
 
为什么close没有效果,数据库连接数不断增加?
 
第一个问题:close关闭的是什么?
在有DBUitls时,close关闭时的PooledDB.connection()产生的连接.
在无DBUtils时,close关闭的时psycopg2.connect()产生的连接.
 
第二个问题: 在有DBUtils时close为什么不会减少连接数?
写了段代码,来看看close是怎么回事
import psycopg2
from DBUtils import PooledDB

def main():
    #mincached,maxconnections限制连接池中的连接数
    pool = PooledDB.PooledDB(psycopg2, port=5432, host='localhost', dbname='postgres', database='tax',
                             user='postgres', password='postgres',
                             mincached=2, maxconnections=2)
    # 从连接池中取一个连接
    conn = pool.connection()
    cur = conn.cursor()
    cur.execute("select count(*) as count from pg_stat_activity")
    rs = cur.fetchone()
    print list(rs)
    cur.close()
    # 不会影响这个连接池的连接数量. pool.close()关闭连接池,会关闭所有连接.
    conn.close()

if __name__ == '__main__':
    while True:
        main()
        time.sleep(1)
发现连接数始终不是变,单个连接的close,不会影响到这个连接池。
 
试试将上面cur.close() conn.close()注释掉,会导致连接数增加吗?
答案也是不会. pool是局部变量,变量销毁,连接会自动关闭。
 
得到结论:
* 单个连接关闭,不会影响连接池
* 连接池变量释放,或者程序停止,连接会自动关闭
 
 
可推断webpy中定有全局变量,不释放而导致连接增加.
 
顺着这个思路阅读web源码,发现web.ctx是个TreadedDict类型
class ThreadedDict(threadlocal):
    """
    Thread local storage.

        >>> d = ThreadedDict()
        >>> d.x = 1
        >>> d.x
        1
        >>> import threading
        >>> def f(): d.x = 2
        ...
        >>> t = threading.Thread(target=f)
        >>> t.start()
        >>> t.join()
        >>> d.x
        1
    """
    # 原因就在这里
    _instances = set()

    def __init__(self):
        ThreadedDict._instances.add(self)

    def __del__(self):
        ThreadedDict._instances.remove(self)

    def __hash__(self):
        return id(self)
    ....
原来它有个静态属性ThreadedDict._instances保存每个实例,而每个实例中又保存web.database产生的PooledDB对象.
所以pool一直不释放。
 
所以原因就是: web.database每次新建一个PooledDB对象,而对象又保存在全局变量TreadedDict._instance中.
由于不断新建连接池,又不释放连接池,最终导致连接数不断增加。
 
 
webpy如何解决这个问题?
使用polling参数,禁止连接池
db = web.database(port=5432, host='localhost', dbn='postgres', db='tax',
                  user='postgres', pw='postgres', pooling=False)
 
这上面得出的结论对oracle和其它类型的数据库同样有效.
 
 
 
Avatar_small
maplye 说:
2016年7月22日 23:12

db = web.database(port=5432, host='localhost', dbn='postgres', db='tax',
user='postgres', pw='postgres')

不要建在循环里面。


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter