|
|
|
|
|
"""
|
|
|
|
|
|
数据缓冲平台 - SQLite 到 MySQL 数据迁移
|
|
|
|
|
|
"""
|
|
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
|
|
from sqlalchemy import create_engine
|
|
|
|
|
|
from sqlalchemy.orm import make_transient, sessionmaker
|
|
|
|
|
|
|
|
|
|
|
|
from app.config import DB_PATH
|
|
|
|
|
|
from app.models import MarketData, SymbolTimestamp
|
|
|
|
|
|
from app.mysql_database import MySQLSessionLocal
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _prepare_records_for_new_session(records):
|
|
|
|
|
|
"""将 ORM 实例转换为可在新 session 中插入的 transient 状态。"""
|
|
|
|
|
|
for record in records:
|
|
|
|
|
|
make_transient(record)
|
|
|
|
|
|
record.id = None
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def migrate_sqlite_to_mysql():
|
|
|
|
|
|
"""从 SQLite 迁移 market_data 和 symbol_timestamps 数据到 MySQL。
|
|
|
|
|
|
|
|
|
|
|
|
幂等设计:当 MySQL 对应表已存在数据时直接跳过,避免重复写入。
|
|
|
|
|
|
返回 True 表示执行了迁移,False 表示跳过或失败。
|
|
|
|
|
|
"""
|
|
|
|
|
|
if MySQLSessionLocal is None:
|
|
|
|
|
|
logger.warning("MySQL 未初始化,跳过数据迁移")
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
sqlite_engine = create_engine(
|
|
|
|
|
|
f"sqlite:///{DB_PATH}",
|
|
|
|
|
|
connect_args={"check_same_thread": False},
|
|
|
|
|
|
)
|
|
|
|
|
|
SQLiteSession = sessionmaker(bind=sqlite_engine)
|
|
|
|
|
|
|
|
|
|
|
|
mysql_session = MySQLSessionLocal()
|
|
|
|
|
|
try:
|
|
|
|
|
|
market_data_count = mysql_session.query(MarketData).count()
|
|
|
|
|
|
symbol_timestamp_count = mysql_session.query(SymbolTimestamp).count()
|
|
|
|
|
|
|
|
|
|
|
|
if market_data_count > 0 or symbol_timestamp_count > 0:
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"MySQL 已存在数据,跳过迁移 "
|
|
|
|
|
|
f"(market_data: {market_data_count}, symbol_timestamps: {symbol_timestamp_count})"
|
|
|
|
|
|
)
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
sqlite_session = SQLiteSession()
|
|
|
|
|
|
try:
|
|
|
|
|
|
market_data_records = sqlite_session.query(MarketData).all()
|
|
|
|
|
|
symbol_timestamp_records = sqlite_session.query(SymbolTimestamp).all()
|
|
|
|
|
|
finally:
|
|
|
|
|
|
sqlite_session.close()
|
|
|
|
|
|
|
|
|
|
|
|
_prepare_records_for_new_session(market_data_records)
|
|
|
|
|
|
_prepare_records_for_new_session(symbol_timestamp_records)
|
|
|
|
|
|
|
|
|
|
|
|
if market_data_records:
|
|
|
|
|
|
mysql_session.add_all(market_data_records)
|
|
|
|
|
|
if symbol_timestamp_records:
|
|
|
|
|
|
mysql_session.add_all(symbol_timestamp_records)
|
|
|
|
|
|
|
|
|
|
|
|
mysql_session.commit()
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"迁移完成: market_data: {len(market_data_records)} 条, "
|
|
|
|
|
|
f"symbol_timestamps: {len(symbol_timestamp_records)} 条"
|
|
|
|
|
|
)
|
|
|
|
|
|
return True
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"数据迁移失败: {e}")
|
|
|
|
|
|
mysql_session.rollback()
|
|
|
|
|
|
return False
|
|
|
|
|
|
finally:
|
|
|
|
|
|
mysql_session.close()
|