Files
deerflow-code/offline-backend-20260512/backend/scripts/audit_index_key_lengths.py
T
吉鑫升andClaude Fable 5 57a87d6322 feat: MySQL 索引长度兼容 + 启动自动补列 + 未发布徽标与查询加载态
后端:
- 索引/唯一键改用 mysql_length 前缀索引,适配 MySQL 767 字节限制(error 1071):knowledge_notes、knowledge_entities、knowledge_graph_blacklist、agent_skills、feedback
- engine.py 新增 _ensure_orm_columns_sync:启动时对比 ORM 元数据,自动 ALTER TABLE 补齐缺失的可空/带默认值列及其索引
- 新增 scripts/audit_index_key_lengths.py 审计全部索引键长度
- 更新 backend/CLAUDE.md 迁移说明

前端:
- 智能体卡片/技能列表:所有者与管理员可见未发布徽标
- 智能体广场:服务端查询进行中时灰显旧结果并悬浮加载提示
- 排行榜入口从 Header 移到欢迎语旁
- threads/hooks.ts:抽取 updateCachedThreadLists 统一处理数组与分页两种缓存形态

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 14:51:50 +08:00

72 lines
2.5 KiB
Python

"""Audit ORM index/unique-key byte sizes against MySQL InnoDB prefix limits.
MySQL with the legacy 767-byte InnoDB index-prefix limit (5.6, or 5.7 with
COMPACT row format) rejects any index whose key exceeds 767 bytes — and
utf8mb4 costs 4 bytes per char, so an indexed VARCHAR key may total at most
191 chars. Modern servers (DYNAMIC row format) allow 3072 bytes = 768 chars.
Run: PYTHONPATH=. .venv/Scripts/python scripts/audit_index_key_lengths.py
"""
from __future__ import annotations
import sys
from sqlalchemy import String, UniqueConstraint
import deerflow.persistence.models # noqa: F401 (registers all ORM models)
from deerflow.persistence.base import Base
LIMIT_OLD = 767
LIMIT_NEW = 3072
BYTES_PER_CHAR = 4 # utf8mb4
def _col_key_bytes(col) -> int | None:
t = col.type
# unwrap TypeDecorator
impl = getattr(t, "impl", None)
if impl is not None:
t = impl
if isinstance(t, String):
return (t.length or 255) * BYTES_PER_CHAR
return 8 # ints/datetimes/floats — small fixed size, close enough
def _key_bytes(col, mysql_length) -> int:
full = _col_key_bytes(col) or 0
if mysql_length is None:
return full
if isinstance(mysql_length, dict):
prefix = mysql_length.get(col.name)
else:
prefix = mysql_length
if prefix is None:
return full
return min(full, int(prefix) * BYTES_PER_CHAR)
def main() -> int:
bad = 0
for table in Base.metadata.sorted_tables:
keys: list[tuple[str, str, list, object]] = []
for idx in table.indexes:
keys.append(("INDEX", idx.name, list(idx.columns), idx.dialect_options["mysql"].get("length")))
for cons in table.constraints:
if isinstance(cons, UniqueConstraint):
keys.append(("UNIQUE", cons.name or "?", list(cons.columns), None))
for kind, name, cols, mysql_length in keys:
total = sum(_key_bytes(c, mysql_length) for c in cols)
if total > LIMIT_OLD:
bad += 1
tag = "EXCEEDS 3072 (fails on ALL MySQL)" if total > LIMIT_NEW else "exceeds 767 (fails on legacy MySQL)"
detail = ", ".join(f"{c.name}({getattr(getattr(c.type, 'impl', c.type), 'length', '?')})" for c in cols)
print(f"{table.name}: {kind} {name} = {total} bytes [{detail}] -- {tag}")
if not bad:
print("OK: every index key fits within 767 bytes (utf8mb4 worst case)")
return 0
if __name__ == "__main__":
sys.exit(main())