每分钟多次更新数据库表的正确方式及cursor已关闭异常排查
问题分析与解决方案
你的当前实现完全错误,出现cursor already closed异常和操作冲突的核心原因是:psycopg2的数据库连接和游标不是线程安全的,绝对不能在多线程间共享全局实例。多线程同时操作同一个连接/游标时,会导致内部状态混乱——比如一个线程关闭了游标,另一个线程还在尝试使用,就会抛出游标已关闭的异常。
不要用全局连接/游标,也不需要加锁
加锁确实能避免多线程冲突,但会让所有更新操作串行执行,在每分钟600次调用的场景下,性能会暴跌,这是治标不治本的方案。正确的思路是让每个线程/每次请求使用独立的连接和游标。
基础改进:每次调用创建独立连接
直接在函数内创建和销毁连接,逻辑简单,对于大多数场景已经够用:
def update_data(name, type, value): # 每次调用创建新连接 conn = psycopg2.connect(dbname='MyDB', user='...', password='...', host='127.0.0.1') try: with conn.cursor() as cursor: cursor.execute("UPDATE Datas SET Value = %s WHERE Name = %s and Type = %s", (value, name, type)) conn.commit() finally: # 无论成功失败都关闭连接 conn.close() def get_all_data(): conn = psycopg2.connect(dbname='MyDB', user='...', password='...', host='127.0.0.1') try: with conn.cursor() as curs: curs.execute('SELECT * FROM Datas') return curs.fetchall() finally: conn.close()
优化方案:使用连接池
针对每分钟600次的高频率调用,推荐用连接池复用连接,减少连接创建/销毁的开销,同时保证线程安全。psycopg2自带连接池实现:
from psycopg2 import pool # 初始化连接池,根据并发量调整最小/最大连接数 # 比如最小2个,最大10个,避免连接过多耗尽数据库资源 connection_pool = pool.SimpleConnectionPool( minconn=2, maxconn=10, dbname='MyDB', user='...', password='...', host='127.0.0.1' ) def update_data(name, type, value): # 从连接池获取连接 conn = connection_pool.getconn() try: with conn.cursor() as cursor: cursor.execute("UPDATE Datas SET Value = %s WHERE Name = %s and Type = %s", (value, name, type)) conn.commit() finally: # 将连接放回连接池,供后续请求复用 connection_pool.putconn(conn) def get_all_data(): conn = connection_pool.getconn() try: with conn.cursor() as curs: curs.execute('SELECT * FROM Datas') return curs.fetchall() finally: connection_pool.putconn(conn)
关键注意事项
- 永远不要在多线程环境下共享psycopg2的连接或游标实例,这是官方明确禁止的行为。
- 连接池的最大连接数不要设置过大,否则会导致数据库连接耗尽,一般根据并发线程数调整即可。
- 务必保证连接使用后放回连接池(或关闭),避免连接泄漏。
内容的提问来源于stack exchange,提问作者Fresto
相关产品推荐
相关产品推荐

