perf(数据库): user 表补两个索引,班级过滤和活跃人数统计不再全表扫
`user` 原来只有主键和 username 唯一约束两个索引 —— 它是 drizzle-kit pull 从 Django 建的表拉出来的,Django 那边也没建过别的。于是两条常走的查询都是全表扫: - `problems/:id/beat-count` 里「近两年登录过的活跃人数」按 is_disabled + last_login 过滤,每打开一次题目详情算一遍; - `routes/classroom.ts` 的 loadClassUsers 按 class_name(或年级前缀 like '241%') 取学生,班级榜、班级对比、AI 学情的排名 scope 都走它。 两千行的表现在扫起来确实不贵,但这是随人数线性涨的那类成本,而且比在应用层加缓存 更根本:不引入陈旧,也不需要考虑失效。 `user_active_idx` 的列序是 (is_disabled, last_login):等值条件在前、范围条件在后。 迁移只有两条 CREATE INDEX,无附带改动;本机 db:migrate 跑过,两个索引都在。 索引效果没法在本机验 —— dev 库只有 12 条提交、几十个用户,规划器无论如何都走 seq scan,要到生产(12 万提交)才看得出来。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XvmqDsZNyUo9P3sFQtoWVB
This commit is contained in:
@@ -664,6 +664,12 @@ export const user = pgTable("user", {
|
||||
className: text("class_name"),
|
||||
}, (table) => [
|
||||
unique("user_username_key").on(table.username),
|
||||
// 「近两年登录过的活跃人数」—— problems/:id/beat-count 每次打开题目详情都要算一遍,
|
||||
// 而这张表原来只有主键和 username 两个索引,那句统计是全表扫。
|
||||
index("user_active_idx").using("btree", table.isDisabled.asc().nullsLast(), table.lastLogin.desc().nullsFirst()),
|
||||
// 按班级 / 按年级(class_name like '241%')取学生:班级榜、班级对比、AI 学情的
|
||||
// 排名 scope 都走它,见 routes/classroom.ts 的 loadClassUsers。
|
||||
index("user_class_name_idx").using("btree", table.className.asc().nullsLast()),
|
||||
]);
|
||||
|
||||
export const problemsetBadge = pgTable("problemset_badge", {
|
||||
|
||||
Reference in New Issue
Block a user