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和其它类型的数据库同样有效.
