webpy+DBUtils是如何将数据库连接数占满的
2014年1月10日 11:41
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和其它类型的数据库同样有效.