"""message 补 (channel, room_id, timestamp) 索引(群聊消息窗超时修复) Revision ID: e6f7a8b9c0d1 Revises: d9e0f1a2b3c4 Create Date: 2026-09-07 内容 ---- 群聊消息窗(/api/messages?channel=&room_id=)**刻意不带 owner**——chat_group 按 room_id 去重后表的 owner 被最后写入的员工覆盖,与消息实际归属错位,带上 会查空(2026-08-22 实测)。于是 ix_message_channel_owner_room_time 的 owner 前缀用不上,优化器退化到 room 单列索引(ix_message_room)取全行再 filter channel + filesort: - 16,290 条群: count 4.7s / 首页 5.1s / ASC 末页 10.7s(EXPLAIN ANALYZE 实测,Sort row IDs 全量 16k 行、128MB buffer pool 下逐行回表磁盘随机读); - 真凶对话 53132846289@chatroom 40,863 条(count+首页+末页串行 ~40s), 前端 axios 15s 超时 → 聊天记录加载失败 ECONNABORTED(2026-09-07 用户报)。 本迁移补 (channel, room_id, timestamp) 三列索引:群查询与单聊同享 seek + 排序直达 + 覆盖 count。模型侧 models.py 同步双声明,防 alembic autogenerate 反 DROP。窄列 BTREE,~94 万行在线建索引预计几十秒 (参照 c53f90b1e6a4 实测 86 万行 16s)。 配套:同日 /api/messages 加 order=desc + before_ts/before_id 游标参数, 聊天窗首屏「DESC 取最新一页 + 反转」、上翻游标,避开 ASC 深 OFFSET 逐行回表(单聊 1 万条末页实测 2.5s → DESC LIMIT 15ms)。 幂等:``sa.inspect`` 先查同名索引,存在即跳过。SQLite/MySQL 通用。 """ from collections.abc import Sequence import sqlalchemy as sa from alembic import op # revision identifiers, used by Alembic. revision: str = 'e6f7a8b9c0d1' down_revision: str | Sequence[str] | None = 'd9e0f1a2b3c4' branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None _INDEX_NAME = 'ix_message_channel_room_time' def _has_index(table: str, index: str) -> bool: insp = sa.inspect(op.get_bind()) return index in {i['name'] for i in insp.get_indexes(table)} def upgrade() -> None: """Upgrade schema.""" if _has_index('message', _INDEX_NAME): return # 已建过(或先手工建过),幂等跳过 op.create_index( _INDEX_NAME, 'message', ['channel', 'room_id', 'timestamp'], ) def downgrade() -> None: """Downgrade schema.""" if not _has_index('message', _INDEX_NAME): return op.drop_index(_INDEX_NAME, table_name='message')