fix(stats): 「今日回答」改按 answered_at 计,不再把置顶算成回答
`TodayCounts` 的今日回答数用 `updated_at >= 0 点` 过滤。GORM 的 Update 一律刷新 updated_at,于是箱主今晚整理一批三个月前的老问题(置顶、 公开/私密切换,一次回答都没发生),/stats 顶部「今日回答」就凭空多出几十条。 实测(临时测试,跑完已删):一条三天前回答的提问,依次 SetPinned / SetPrivate / SetPublic 之后,today answers 从 1 变成 2。 改成 `answered_at >= since`(首次回答时间,改写回答不重置): 口径变成「今天首次被回答的提问数」,与页面文案「今日回答 / Answered today」和「回答耗时按首次回答算」都对齐。历史行 answered_at 为空时 `>=` 不成立、自然不计数 —— 那些行本来也不可能「今天被回答」。 性能:answered_at 没有索引,这条 COUNT 是全表过滤;但改动前的 updated_at 同样没有索引,本来就全是 Seq Scan。20 万行实测 旧 36.7ms / 新 38.0ms(差 3.5%,噪声级),执行计划都是 Seq Scan + Aggregate。页面另有 60 秒缓存,不需要为它加索引; 注释里写了真慢了再加 idx_questions_answered_at。 新增 TestTodayAnswersIgnoreNonAnswerWrites(改前红:期望 1 得到 2; 改后绿)。
This commit is contained in:
@@ -188,10 +188,18 @@ func (db *siteAnalytics) SiteAnalytics(ctx context.Context, now time.Time) (*Sit
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// TodayCounts 取今日提问数与今日回答数,只扫当天数据(走 created_at 索引)。
|
||||
// TodayCounts 取今日提问数与今日回答数。
|
||||
//
|
||||
// 口径:回答数按「今天被回答过」计,用的是 updated_at —— 置顶等写操作也会刷新它,
|
||||
// 因此这个数偏大。页面只把它当「今天有多少动静」的量级参考,不做精确承诺。
|
||||
// 口径:
|
||||
// - 今日提问 = created_at 落在今天的提问,走 idx_questions_created_at;
|
||||
// - 今日回答 = answered_at 落在今天的提问,即「今天首次被回答」。
|
||||
// 不能拿 updated_at 当回答时间:置顶、公开/私密切换、标记已读都会刷新它
|
||||
// (GORM 的 Update 一律更新 updated_at),箱主晚上整理一批三个月前的老问题
|
||||
// —— 一次回答都没有 —— 页面上「今日回答」就会凭空多出几十条。
|
||||
//
|
||||
// answered_at 没有索引,这一条是全表过滤(与改动前的 updated_at 同级别);
|
||||
// 十万行量级约几十毫秒,页面另有 60 秒缓存,够用。真慢了再加
|
||||
// idx_questions_answered_at,不必为这个查询动表结构。
|
||||
func (db *siteAnalytics) TodayCounts(ctx context.Context, since time.Time) (int64, int64, error) {
|
||||
var questions, answers int64
|
||||
if err := db.WithContext(ctx).Model(&Question{}).
|
||||
@@ -199,7 +207,7 @@ func (db *siteAnalytics) TodayCounts(ctx context.Context, since time.Time) (int6
|
||||
return 0, 0, errors.Wrap(err, "count today's questions")
|
||||
}
|
||||
if err := db.WithContext(ctx).Model(&Question{}).
|
||||
Where("answer <> '' AND updated_at >= ?", since).Count(&answers).Error; err != nil {
|
||||
Where("answer <> '' AND answered_at >= ?", since).Count(&answers).Error; err != nil {
|
||||
return 0, 0, errors.Wrap(err, "count today's answers")
|
||||
}
|
||||
return questions, answers, nil
|
||||
|
||||
Reference in New Issue
Block a user