NULL 那条是这轮最实在的纠错,但只给列加 NOT NULL DEFAULT 0 还不够——INSERT ... SELECT 那一侧的表达式本身也得包 IFNULL 或 COALESCE,两边都防才稳。
因为严格模式下往 NOT NULL 列塞 NULL 会直接报错、整批 upsert 失败;非严格模式则静默转 0 再甩个 warning,后者更坏,warning 没人看,等于你还是不知道。所以判据最好写成 EXISTS(...) 或 MAX(CASE WHEN ... THEN 1 ELSE 0 END) 这种天然非 NULL 的形态,别指望列定义兜底。
回访率拆成两条 SELECT DISTINCT 我同意,但「走覆盖索引」和「走从库」得分开看:这活儿一天跑一次,扔低峰主库跑就完了,从库留给前台查询。硬要上从库,延迟会让本周集合偏小、回访率被系统性低估;更麻烦的是两条查询撞上不同时刻的延迟,同一个日期范围跑两次结果不一样——报表数据不可复现,比慢难处理得多。
recordDailyAction 这个统一入口的写法支持。两个细节:签名带上 $date 默认今天(不然回填脚本没地方塞);内部走一条 col = col | VALUES(col) 的 upsert,别先 SELECT 再 UPDATE,并发重跑会丢更新。type 白名单做成数组常量,加新 type = 改数组 + 一份回填脚本,正好对上你说的写进升级说明。
JSON 摘要建议断言成「最新时间戳距今 < N 分钟」而不是「字段存在」,另外带个 source: lazy/cron,不然分不清「没流量所以没跑」和「跑了但挂了」。老用户豁免别新造一套判据,直接复用注册天数 + 历史零标记,省一份维护成本。
最后提一句:stat_user_daily 建表语句进增量 DDL 是对的,但别忘了一起补索引的 DDL——表建了、idx_date_has_any 没建,线上就是全表扫,而且这种漏建不会有任何报错提示。

正式会员
认证极客










