Peewee与Dash集成疑问:回调函数内数据库连接无法复用的原因
问题:Dash应用中Peewee连接池无法复用连接导致超出最大连接数
在使用Peewee管理数据库的Plotly Dash应用中,出现playhouse.pool.MaxConnectionsExceeded: Exceeded maximum connections.错误,通过PostgreSQL查询可见连接数持续增长:
ariobot=# SELECT count(*) FROM pg_stat_activity where client_port > 0 and application_name = 'test 1'; count ------- 21 (1 row)
复现代码(test1.py)
# test1.py from peewee import Model, TextField from playhouse.pool import PooledPostgresqlExtDatabase db = PooledPostgresqlExtDatabase( "postgresql://postgres@127.0.0.1:5432/ariobot", autoconnect=True, application_name='test 1' ) class BaseModel(Model): class Meta: database = db class Test(BaseModel): test = TextField() db.create_tables([Test]) from dash import register_page, dash_table, callback, dcc, Input, Output, html, Dash app = Dash(__name__) app.layout = html.Div( [ html.Div(id="value"), dcc.Interval(id="db_query_interval", interval=10000, n_intervals=0), ] ) @callback( Output("value", "children"), Input("db_query_interval", "n_intervals"), ) def do_stuff(interval): db = Test.select() for db in db: val = db.test return val if __name__ == "__main__": app.run(debug=True)
对比测试(test2.py)
以下循环代码能正常复用连接,符合预期:
# test2.py from peewee import Model, TextField from playhouse.pool import PooledPostgresqlExtDatabase db = PooledPostgresqlExtDatabase( "postgresql://postgres@127.0.0.1:5432/ariobot", autoconnect=True, application_name='test 2' ) class BaseModel(Model): class Meta: database = db class Test(BaseModel): test = TextField() db.create_tables([Test]) from time import sleep while True: db = Test.select() for db in db: val = db.test print(val) sleep(1)
对应的PostgreSQL查询结果:
ariobot=# SELECT count(*) FROM pg_stat_activity where client_port > 0 and application_name = 'test 2'; count ------- 2 (1 row)
提问:为何test1.py中的数据库连接无法被复用?
原因分析与解决方案
核心原因
- 变量名冲突:回调函数中用
db = Test.select()覆盖了全局的数据库连接实例,导致无法通过原实例管理连接的归还。 - Dash的多线程/多进程环境:Dash默认debug模式会启动reloader(额外进程),且回调可能在不同线程执行。Peewee连接池默认基于线程本地存储,每个线程会创建新连接,但如果连接未被正确归还,就会持续占用直至池上限。
- 未显式管理连接生命周期:
test2.py是单线程运行,查询后连接会自动归还池;但Dash回调中没有通过上下文管理器或显式调用确保连接归还,导致连接泄漏。
解决方案
1. 修复变量名冲突
回调中不要用db作为查询结果变量,避免覆盖全局数据库实例:
query = Test.select() # 替换原db = Test.select()
2. 使用上下文管理器确保连接归还
用db.atomic()或db.connection()包裹数据库操作,无论是否异常,连接都会自动归还池:
@callback( Output("value", "children"), Input("db_query_interval", "n_intervals"), ) def do_stuff(interval): query = Test.select() val = "" with db.atomic(): for row in query: val = row.test return val
3. 关闭Dash的reloader(debug模式下)
debug模式的reloader会启动额外进程,占用额外连接,可关闭此功能:
if __name__ == "__main__": app.run(debug=True, use_reloader=False)
4. 显式释放连接(备选)
如果不使用上下文管理器,可手动获取并释放连接:
@callback( Output("value", "children"), Input("db_query_interval", "n_intervals"), ) def do_stuff(interval): conn = db.connection() try: query = Test.select() val = "" for row in query: val = row.test return val finally: db.release(conn)
内容的提问来源于stack exchange,提问作者Jason
相关产品推荐
相关产品推荐

