100 lines
3.9 KiB
Python
100 lines
3.9 KiB
Python
|
|
# -*- 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()
|