tradingSystem/scripts/migrate_param_value_text.py

100 lines
3.9 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
"""
迁移: pms_runtime_param.param_value VARCHAR(200) 放宽为 TEXT
================================================================
运行:
docker compose run --rm --no-deps pms-web python scripts/migrate_param_value_text.py # 演练, 只打印
docker compose run --rm --no-deps pms-web python scripts/migrate_param_value_text.py --yes # 实际执行
为什么要迁:
这一列存的不全是短标量 清场名单 (PMS_EXIT_CLEANUP_DONE, 闭仓票代码的 JSON 数组)
回放游标 (PMS_REPLAY_CURSOR, v2 起带 seen 去重字典) 都随运行越长越大真机 2026-08-31
实测: 名单 30 只票就超 200 字符, 两处写入每分钟各报一次 1406 截断错, 游标推不动
清场防呆标记存不上 (后者失守时, 闭仓后新下的建仓单会被清场误撤)
为什么单独一个脚本, 不放进 init_db:
init_db.py 只认 `CREATE TABLE IF NOT EXISTS` 与幂等 INSERT, ALTER 语句会被它判成残句
整批中止; CREATE IF NOT EXISTS 对已存在的表一个字都不改建表语句 (ddl_pms_v1.sql)
已同步改成 TEXT, **新库直接就有**, 不需要跑本脚本
幂等: 列已是 TEXT 时自动跳过, 重复跑安全绕过 db.session 的单表守卫 (直接用引擎),
migrate_archived_at 同一套做法 ALTER ShardingSphere-Proxy 拒绝, 脚本会把完整
语句打出来, 直接拿去物理库 (my_quant_db) 执行即可
"""
import argparse
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
TABLE = "pms_runtime_param"
COL = "param_value"
TARGET_TYPE = "text"
def _col_type(conn, table: str, col: str):
from sqlalchemy import text
rows = conn.execute(text(f"SHOW COLUMNS FROM {table}")).fetchall()
for r in rows:
if r[0] == col:
return str(r[1] or "").lower()
return None
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--yes", action="store_true", help="确认执行 (缺省只演练)")
args = ap.parse_args()
from sqlalchemy import text
from app.db.session import TABLE_PREFIX, get_engine, map_tables
eng = get_engine("proxy")
if TABLE_PREFIX:
print(f"[双实例] 表名前缀 {TABLE_PREFIX!r} 生效: 实际迁移的是 {TABLE_PREFIX}{TABLE}")
real_table = map_tables(TABLE)
try:
with eng.connect() as c:
cur = _col_type(c, real_table, COL)
except Exception as e:
print(f"FAIL: 查列失败 {type(e).__name__}: {e} (表不存在就先跑 init_db)")
sys.exit(1)
if cur is None:
print(f"FAIL: {real_table} 没有 {COL} 列 —— 表结构与预期不符, 先人工核对")
sys.exit(1)
print(f"当前类型: {real_table}.{COL} = {cur}")
if cur.startswith(TARGET_TYPE):
print("无需迁移: 已是 TEXT。")
return
stmt = map_tables(
f"ALTER TABLE {TABLE} MODIFY COLUMN {COL} TEXT NOT NULL "
f"COMMENT '参数值: 标量或 JSON; 2026-08-31 由 VARCHAR(200) 放宽 (清场名单/回放游标会长大)'")
if not args.yes:
print("\n[演练模式] 未执行任何语句。确认无误后加 --yes 重跑。将执行:")
print(" " + stmt)
return
try:
with eng.begin() as c:
c.execute(text(stmt))
print("OK ALTER 执行成功")
except Exception as e:
print(f"FAIL {type(e).__name__}: {e}")
print(f"\n完整语句如下 —— 可直接拿到物理库 (my_quant_db) 执行:\n{stmt};")
sys.exit(1)
with eng.connect() as c:
now = _col_type(c, real_table, COL)
if now and now.startswith(TARGET_TYPE):
print(f"ALL OK: {real_table}.{COL} 已是 {now}。清场名单与回放游标的写入即刻恢复, 无需重启。")
else:
print(f"FAIL: 迁移后类型仍为 {now}")
sys.exit(1)
if __name__ == "__main__":
main()