From 216c88d13895f0e043cd9b8f342fc53c827eb041 Mon Sep 17 00:00:00 2001 From: Windpicker-owo <3431391539@qq.com> Date: Sat, 1 Nov 2025 16:22:54 +0800 Subject: [PATCH] =?UTF-8?q?fix(database):=20=E4=BF=AE=E5=A4=8D=20detached?= =?UTF-8?q?=20=E5=AF=B9=E8=B1=A1=E5=BB=B6=E8=BF=9F=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E7=9A=84=20greenlet=20=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题: - CRUD 返回的对象在 session 关闭后变为 detached 状态 - 访问属性时 SQLAlchemy 尝试延迟加载,但没有 session - 导致: greenlet_spawn has not been called 根本原因: - SQLAlchemy 对象在 session 外被访问 - 延迟加载机制尝试在非异步上下文中执行异步操作 修复方案: 1. CRUDBase.get_by(): 在 session 内预加载所有列 2. CRUDBase.get_multi(): 在 session 内预加载所有实例的所有列 3. PersonInfo.get_value(): 添加异常处理,防御性编程 影响: - 所有通过 CRUD 获取的对象现在都完全加载 - 避免了 detached 对象的延迟加载问题 - 可能略微增加初始查询时间,但避免了运行时错误 --- src/common/database/api/crud.py | 25 +++++++++++++++++++++---- src/person_info/person_info.py | 22 ++++++++++++++-------- src/person_info/relationship_fetcher.py | 2 +- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/common/database/api/crud.py b/src/common/database/api/crud.py index b3b06e93e..e652072b5 100644 --- a/src/common/database/api/crud.py +++ b/src/common/database/api/crud.py @@ -113,10 +113,19 @@ class CRUDBase: result = await session.execute(stmt) instance = result.scalar_one_or_none() - # 写入缓存 - if instance is not None and use_cache: - cache = await get_cache() - await cache.set(cache_key, instance) + if instance is not None: + # 触发所有列的加载,避免 detached 后的延迟加载问题 + # 遍历所有列属性以确保它们被加载到内存中 + for column in self.model.__table__.columns: + try: + getattr(instance, column.name) + except Exception: + pass # 忽略访问错误 + + # 写入缓存 + if use_cache: + cache = await get_cache() + await cache.set(cache_key, instance) return instance @@ -166,6 +175,14 @@ class CRUDBase: result = await session.execute(stmt) instances = result.scalars().all() + # 触发所有实例的列加载,避免 detached 后的延迟加载问题 + for instance in instances: + for column in self.model.__table__.columns: + try: + getattr(instance, column.name) + except Exception: + pass # 忽略访问错误 + # 写入缓存 if use_cache: cache = await get_cache() diff --git a/src/person_info/person_info.py b/src/person_info/person_info.py index c6a60f5f9..f5b4818bf 100644 --- a/src/person_info/person_info.py +++ b/src/person_info/person_info.py @@ -563,10 +563,6 @@ class PersonInfoManager: logger.debug("get_value获取失败:person_id不能为空") return None - # 使用CRUD进行查询 - crud = CRUDBase(PersonInfo) - record = await crud.get_by(person_id=person_id) - model_fields = [column.name for column in PersonInfo.__table__.columns] if field_name not in model_fields: @@ -577,11 +573,21 @@ class PersonInfoManager: logger.debug(f"get_value查询失败:字段'{field_name}'未在SQLAlchemy模型和默认配置中定义。") return None + # 使用CRUD进行查询 + crud = CRUDBase(PersonInfo) + record = await crud.get_by(person_id=person_id) + if record: - value = getattr(record, field_name) - if value is not None: - return value - else: + # 在访问属性前确保对象已加载所有数据 + # 使用 try-except 捕获可能的延迟加载错误 + try: + value = getattr(record, field_name) + if value is not None: + return value + else: + return copy.deepcopy(person_info_default.get(field_name)) + except Exception as e: + logger.warning(f"访问字段 {field_name} 失败: {e}, 使用默认值") return copy.deepcopy(person_info_default.get(field_name)) else: return copy.deepcopy(person_info_default.get(field_name)) diff --git a/src/person_info/relationship_fetcher.py b/src/person_info/relationship_fetcher.py index 9091f020a..cd6be1df4 100644 --- a/src/person_info/relationship_fetcher.py +++ b/src/person_info/relationship_fetcher.py @@ -200,7 +200,7 @@ class RelationshipFetcher: "user_aliases": relationship.user_aliases, "relationship_text": relationship.relationship_text, "preference_keywords": relationship.preference_keywords, - "relationship_score": relationship.affinity, + "relationship_score": relationship.relationship_score, } # 5.1 用户别名