fix(init_db): 启动迁移失败不再静默——logger.exception 带 traceback + schema 覆盖校验

2026-09-26 事故复盘:AUTO_MIGRATE=true 但启动迁移在 stamp 之前就抛异常,
except 里 logger.error(f"{e}") 丢掉了 traceback,docker logs 只剩一句
"数据库初始化失败",prod 缺 stp_files.product_id 等列导致上传全挂,
无从定位。两个修复:

- 初始化异常改用 logger.exception,完整 traceback 落日志
- 新增启动期 _verify_schema_coverage:模型声明的 32 张表全部列必须真实
  存在于 DB,缺失即 error 点名(只查缺不查多,遗留列不报);迁移后自动
  执行,只读、自身异常不影响启动

已对生产库实跑验证:32 张表全部通过。

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
2026-09-26 23:11:30 +08:00
parent 021bf311c1
commit 4a5dc61a02
+54 -1
View File
@@ -184,6 +184,51 @@ async def create_admin_user(session):
logger.info(f"创建了管理员账户: {settings.ADMIN_USERNAME}")
async def _verify_schema_coverage() -> None:
"""启动期 schema 校验:模型声明的表/列必须真实存在于 DB。
背景(2026-09-26 事故):AUTO_MIGRATE 启动迁移曾静默失败,prod 库缺
stp_files.product_id 等列,上传全挂两天无启动日志线索。此校验保证
『迁移被跳过 / stamp 基线掩盖未应用增量』这类漂移在启动时即被点名。
只查缺、不查多:DB 里的遗留列/表(如 users.is_superuser)是历史产物,不管。
发现缺失只记 error 不抛——服务照常起,但 docker logs 必有醒目线索。
"""
import moldinsight.models # noqa: F401
import inventory.models # noqa: F401
from shared.models.base import Base
async with db_manager.engine.connect() as conn:
rows = (await conn.execute(text(
"SELECT table_name, column_name FROM information_schema.columns "
"WHERE table_schema='public'"
))).all()
db_cols: dict = {}
for tn, cn in rows:
db_cols.setdefault(tn, set()).add(cn)
missing = []
for tname, table in Base.metadata.tables.items():
present = db_cols.get(tname)
if present is None:
missing.append(f"整表缺失: {tname}")
continue
absent = {c.name for c in table.columns} - present
if absent:
missing.append(f"{tname} 缺列: {sorted(absent)}")
if missing:
logger.error(
"schema 校验失败:模型声明了 %d 处 DB 缺失(迁移链与实际 schema 不一致,"
"请人工执行 alembic upgrade head 或核对基线):%s",
len(missing), "; ".join(missing),
)
else:
logger.info(
"schema 校验通过:模型 %d 张表的全部列均存在于 DB", len(Base.metadata.tables)
)
async def init_database(keep_connected: bool = True):
"""初始化数据库"""
try:
@@ -195,6 +240,12 @@ async def init_database(keep_connected: bool = True):
"AUTO_MIGRATE=false:跳过启动期 alembic 迁移,"
"schema 由部署流程单点执行(alembic CLI 或 python -m shared.database.init_db)"
)
# 迁移后校验(只读、不抛):迁移链与实际 schema 脱节时在启动日志直接点名
try:
await _verify_schema_coverage()
except Exception:
logger.exception("schema 校验自身异常(不影响启动流程)")
async with db_manager.session() as session:
perm_map = await init_permissions(session)
@@ -223,7 +274,9 @@ async def init_database(keep_connected: bool = True):
return True
except Exception as e:
logger.error(f"数据库初始化失败: {e}")
# logger.exception 而非 error(f"{e}"):吞掉 traceback 曾让启动迁移静默
# 失败两天无从排查(2026-09-26 事故)
logger.exception(f"数据库初始化失败: {e}")
print(f"数据库初始化失败: {e}")
return False
finally: