"""5 表 updated_at 窄索引——/api/analytics/health MAX 消除全表扫 Revision ID: c3d4e5f6a7b8 Revises: f0a1b2c3d4e5 Create Date: 2026-09-04 health 端点每页面加载对 6 表各 MAX 一次;5 表 updated_at 无索引时 MySQL 全表扫(media 5万宽行在 128MB buffer pool 下最伤)。单列窄索引 各几 MB,MAX 变 O(1) 索引定位。幂等:_has_index 先查。SQLite/MySQL 通用。 模型侧 models.py 同步双声明,防 alembic autogenerate 反 DROP。 """ from collections.abc import Sequence import sqlalchemy as sa from alembic import op revision: str = "c3d4e5f6a7b8" down_revision: str | Sequence[str] | None = "f0a1b2c3d4e5" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None # (表名, 索引名) —— 与 models.py 各模型 __table_args__ 一字对应 _INDEXES: tuple[tuple[str, str], ...] = ( ("customer", "ix_customer_updated_at"), ("chat_group", "ix_chat_group_updated_at"), ("phone_call", "ix_phone_call_updated_at"), ("media", "ix_media_updated_at"), ("moments_post", "ix_moments_post_updated_at"), ) def _has_index(bind, table: str, index: str) -> bool: insp = sa.inspect(bind) if not insp.has_table(table): return False return index in {ix["name"] for ix in insp.get_indexes(table)} def upgrade() -> None: bind = op.get_bind() for table, index in _INDEXES: if not _has_index(bind, table, index): op.create_index(index, table, ["updated_at"]) def downgrade() -> None: bind = op.get_bind() for table, index in _INDEXES: if _has_index(bind, table, index): op.drop_index(index, table_name=table)