今天,我在使用ZODB的python应用程序上发现了一个错误。 试图找到为什么我的应用程序死机的原因,我发现ZODB是原因。
将日志记录设置为调试,似乎在提交时,ZODB会找到2个连接,然后开始冻结。
INFO:ZEO.ClientStorage:('127.0.0.1', 8092) Connected to storage: ('localhost', 8092)
DEBUG:txn.140661100980032:new transaction
DEBUG:txn.140661100980032:commit
DEBUG:ZODB.Connection:Committing savepoints of size 1858621925
DEBUG:discord.gateway:Keeping websocket alive with sequence 59.
DEBUG:txn.140661100980032:commit <Connection at 7fee2d080fd0>
DEBUG:txn.140661100980032:commit <Connection at 7fee359e5cc0>
当我是ZODB初学者时,关于如何解决/如何更深入的任何想法?
它似乎与并发提交有关。
我相信打开一个新的连接会启动一个专门的事务管理器,但事实并非如此。在不指定事务管理器的情况下启动新连接时,将使用本地(与线程上的其他连接共享)。
我的代码:
async def get_connection():
return ZEO.connection(8092)
async def _message_db_init_aux(self, channel, after=None, before=None):
connexion = await get_connection()
root = connexion.root()
messages = await some_function_which_return_a_list()
async for message in messages:
# If author.id doesn't exist on the data, let's initiate it as a Tree
if message.author.id not in root.data: # root.data is a BTrees.OOBTree.BTree()
root.data[message.author.id] = BTrees.OOBTree.BTree()
# Message is a defined classed inherited from persistant.Persistant
root.data[message.author.id][message.id] = Message(message.id, message.author.id, message.created_at)
transaction.commit()
connexion.close()
答案 0 :(得分:0)
如果不精确,则使用本地事务管理器。 如果在同一线程上打开多个连接,则必须精确调整要使用的事务管理器。默认情况下
transaction.commit()
是本地交易经理。
connection.transaction.manager.commit()
将使用专门用于交易的交易管理器(而不是本地的)。
有关更多信息,请检查http://www.zodb.org/en/latest/guide/transactions-and-threading.html
答案 1 :(得分:0)
不要跨连接重复使用事务管理器。每个连接都有其自己的事务管理器,使用。
您的代码当前创建连接,然后提交。而不是创建连接,而是要求数据库为您创建一个事务管理器,然后该事务管理器管理自己的连接。事务管理器可用作上下文管理器,这意味着在上下文结束时自动提交对数据库的更改。
此外,通过对每个事务使用ZEO.connection()
,您将强制ZEO创建一个具有全新缓存和连接池的全新客户端对象。通过使用ZEO.DB()
并缓存结果,可以创建一个客户端,从该客户端可以合并和重用连接,并使用本地缓存来加快事务处理。
我将代码更改为:
def get_db():
"""Access the ZEO database client.
The database client is cached to take advantage of caching and connection pooling
"""
db = getattr(get_db, 'db', None)
if db is None:
get_db.db = db = ZEO.DB(8092)
return db
async def _message_db_init_aux(self, channel, after=None, before=None):
with self.get_db().transaction() as conn:
root = conn.root()
messages = await some_function_which_return_a_list()
async for message in messages:
# If author.id doesn't exist on the data, let's initiate it as a Tree
if message.author.id not in root.data: # root.data is a BTrees.OOBTree.BTree()
root.data[message.author.id] = BTrees.OOBTree.BTree()
# Message is a defined classed inherited from persistant.Persistant
root.data[message.author.id][message.id] = Message(
message.id, message.author.id, message.created_at
)
数据库对象上的.transaction()
方法在进入上下文的那一刻(with
导致调用__enter__
)和{{ 1}}块结束事务的提交,并再次将连接释放到池中。
请注意,我使用了同步with
方法; ZEO客户端代码上的呼叫签名是完全同步的。可以安全地从异步代码中调用它们,因为内幕,实现在整个循环中都使用def get_db()
,在同一循环中使用回调和任务,而实际的I / O则推迟到单独的任务中进行。