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:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user