From 27974474f80e7320dbf4bada4033a6dbb5ee21e2 Mon Sep 17 00:00:00 2001 From: Lxy Date: Sat, 4 Jul 2026 16:15:35 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20MySQL=20session=20?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E5=A4=B1=E8=B4=A5=E9=99=8D=E7=BA=A7=E5=92=8C?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E5=AF=B9=E8=B1=A1=20detached=20=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/analysis_db.py | 16 +++++++++++++++- app/analysis_migration.py | 6 +++--- tests/test_analysis_storage.py | 20 ++++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/app/analysis_db.py b/app/analysis_db.py index 521b0fe..c115945 100644 --- a/app/analysis_db.py +++ b/app/analysis_db.py @@ -38,11 +38,25 @@ def get_analysis_db(): storage = get_storage_manager() if MySQLSessionLocal is not None and storage.check_mysql(): + db = None try: db = MySQLSessionLocal() yield db + except Exception as e: + logger.warning(f"创建 MySQL analysis session 失败,降级到 SQLite: {e}") + if db is not None: + try: + db.close() + except Exception: + pass + db = AnalysisSessionLocal() + yield db finally: - db.close() + if db is not None: + try: + db.close() + except Exception: + pass else: db = AnalysisSessionLocal() try: diff --git a/app/analysis_migration.py b/app/analysis_migration.py index 588c95a..1c4f50d 100644 --- a/app/analysis_migration.py +++ b/app/analysis_migration.py @@ -3,7 +3,7 @@ """ import logging -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import sessionmaker, make_transient from app.analysis_db import analysis_engine, AnalysisBase from app.analysis_models import ( @@ -104,8 +104,8 @@ def _write_mysql_analysis_data(data): continue for record in records: - # 创建新对象,避免绑定到 SQLite session - db.expunge(record) + # 将对象从原 session 中脱离,使其可加入新 session + make_transient(record) db.add(record) db.commit() diff --git a/tests/test_analysis_storage.py b/tests/test_analysis_storage.py index b92a88d..23aeee4 100644 --- a/tests/test_analysis_storage.py +++ b/tests/test_analysis_storage.py @@ -149,3 +149,23 @@ class TestAnalysisMigration: assert result is True mock_write.assert_called_once_with({"ai_analysis_cache": [{"id": 1, "symbol": "AG2606"}]}) + + def test_get_analysis_db_falls_back_to_sqlite_when_mysql_session_fails(self): + """MySQL Session 创建失败时降级到 SQLite。""" + from app.analysis_db import get_analysis_db + + mysql_session_maker = MagicMock(side_effect=RuntimeError("MySQL 连接失败")) + sqlite_session = MagicMock() + sqlite_session_maker = MagicMock(return_value=sqlite_session) + + with patch("app.analysis_db.MySQLSessionLocal", mysql_session_maker): + with patch("app.analysis_db.AnalysisSessionLocal", sqlite_session_maker): + with patch("app.analysis_db.get_storage_manager") as mock_sm: + sm = MagicMock() + sm.check_mysql.return_value = True + mock_sm.return_value = sm + + db = next(get_analysis_db()) + + assert db is sqlite_session + mysql_session_maker.assert_called_once()