错误日志
Traceback (most recent call last):File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/api.py", line 983, in getcache_value = field_cache[record._ids[0]]
KeyError: 3During handling of the above exception, another exception occurred:Traceback (most recent call last):File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/fields.py", line 1132, in __get__value = env.cache.get(record, self)File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/api.py", line 990, in getraise CacheMiss(record, field)
odoo.exceptions.CacheMiss: 'im.auto.test.api(3,).code'During handling of the above exception, another exception occurred:Traceback (most recent call last):File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/api_server/handler.py", line 110, in handle_requestresult = f(self, *args, **kwargs, context=context)File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/api_server/decorator.py", line 506, in wrapperres = function(*args, **kwargs)File "/Users/mzwang/Documents/codes/python/im_test_system/addons/im_pressure/api/api_auto_test_api.py", line 109, in ts_api_im_auto_test_api_listtotal, data = auto_test_api_obj.list_auto_test_api_func(File "/Users/mzwang/Documents/codes/python/im_test_system/addons/im_pressure/models/im_auto_test_api_model.py", line 216, in list_auto_test_api_funcreturn total, [File "/Users/mzwang/Documents/codes/python/im_test_system/addons/im_pressure/models/im_auto_test_api_model.py", line 218, in <listcomp>"code": model.code,File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/fields.py", line 1158, in __get__recs._fetch_field(self)File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/models.py", line 3157, in _fetch_fieldself._read(fnames)File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/models.py", line 3234, in _readcr.execute(query_str, params + [sub_ids])File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/sql_db.py", line 312, in execute_logger.debug("query: %s", self._format(query, params))File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/sql_db.py", line 303, in _formatencoding = psycopg2.extensions.encodings[self.connection.encoding]File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/sql_db.py", line 470, in __getattr__return getattr(self._obj, name)File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/sql_db.py", line 469, in __getattr__raise psycopg2.InterfaceError("Cursor already closed")
psycopg2.InterfaceError: Cursor already closed
2024-04-16 09:48:32,253 94676 DEBUG im_test_system_dev odoo.sql_db: SUM from:0:00:00/0 [201]
突然出现的项目bug,之前做的模块都没有出现这种情况,新增了一个基础接口模块,突然蹦出来,而且报错及其不稳定,有时候可以刷新,有时候连续几次都没有报错。但一定会出现就是了。
解决思路:
1. 看日志定位问题是什么
2. 发现日志有两处error,一一排查,第一个是odoo 缓存,第二个是pg cursor
3. 浪费了一下午的时间发现第一个缓存不是根本原因,只是因为之前就遇到过这个缓存问题,导致第一时间排查此
4. 继续排查第二个问题。
5. 报错信息为:psycopg2.InterfaceError: Cursor already closed 报错现象为单接口请求没问题,前端多个接口一起并发请求就会报错。所以第一时间想到的就是多线程情况下数据库连接异常关闭。
6. 测试定位排除:- 优先排除前端问题- 更换数据库,将本地数据库替换为测试环境的,依旧出现(排除odoo升级数据库异常)- 更换模块查询,没有问题(此时因为没有能发现稳定复现的机制,所以这个并没有什么意义)- 替换报错代码,注释掉则不会报错,替换函数内orm操作,发现也会出现异常。于是替换逻辑,仅用 time.sleep 模拟耗时,报错频率增加。(更加怀疑是odoo 本身的问题)
7. 向自己怀疑的目标前进,开始扒源码,看odoo对数据库连接的实现。
8. 打印日志,打印线程号打印cr对象。
9. 发现在出现异常的情况下,都会有数据库连接被释放。尝试在列表页接口添加 time.sleep ,竟然可以稳定复现报错。于是猜测是在并发执行过程中,A请求执行线程并没有完全释放掉cr,后一个B请求就已经拿到了同一个cr,如果B中存在耗时操作,A请求此时释放cr,B耗时操作完成着执行数据库查询,就会发现cr对象已经被释放。出现报错:psycopg2.InterfaceError: Cursor already closed
如何解决
1. 每个线程拿到的cr数据库连接,都应该是不同的才对使用线程锁(Thread Lock): 在访问 cr 之前,可以使用线程锁来确保一次只有一个线程可以获取到 cr。这样可以防止多个线程同时操作 cr,从而避免数据竞争和不一致性。为每个线程创建独立的数据库连接和游标: 可以为每个线程创建独立的数据库连接和游标,这样每个线程都可以独立地操作数据库,不会影响其他线程的操作。这种方法可以确保并发性,但也会增加数据库连接和资源的开销。使用连接池(Connection Pool): 可以使用连接池来管理数据库连接,确保每个线程获取到的连接都是唯一的。连接池可以控制并发访问数据库的数量,以及确保连接的复用和有效性。这样可以避免因为连接过多而导致数据库性能下降,同时也可以减少连接的创建和销毁开销。使用数据库事务(Database Transaction): 在需要操作数据库的地方,可以使用数据库事务来确保一系列操作的原子性和一致性。这样可以在一次事务中执行多个操作,并确保这些操作要么全部成功,要么全部失败,从而避免数据的不一致性。使用线程局部存储(Thread Local Storage,TLS):在每个线程中维护一个独立的数据库连接对象。Python 提供了 threading.local() 类来实现线程局部存储,你可以在每个线程中创建一个独立的数据库连接对象,并在处理请求时使用该对象。这样可以确保每个线程使用的是独立的数据库连接,避免了多个线程之间的竞争和冲突。使用连接池(Connection Pool):连接池是一种管理数据库连接的机制,它会在应用程序启动时创建一定数量的数据库连接,并将它们保存在一个池中。每个线程需要使用数据库连接时,可以从连接池中获取一个连接,并在使用完毕后将其放回连接池。这样可以避免多个线程同时使用同一个连接,提高了数据库连接的复用性和效率。使用同步机制:在获取数据库连接时使用同步机制(如互斥锁)来确保只有一个线程可以获取连接,其他线程需要等待。这样可以避免多个线程同时获取同一个连接,但会影响性能。使用数据库连接的上下文管理器:在使用数据库连接时,可以通过上下文管理器来确保连接在使用完毕后被正确关闭。Python 中的 with 语句可以很方便地实现这一点,确保在退出 with 块时自动关闭连接。