DAU应按user_id去重计算当日有有效行为的独立用户数,而非登录请求次数;WAU是7天内活跃用户的并集;次日留存分母为当日首次活跃用户,需统一时区避免数据错位。

DAU怎么算才不漏掉重复登录的用户
DAU(日活跃用户)不是简单统计当天所有登录请求次数,而是按 user_id 去重后的实际人数。常见错误是直接 count(*) 所有 login_log 行,结果把同一人多次登录、换设备登录全算成“多个用户”。
正确做法是查当天有任意一次有效行为(登录、点击、提交等)的唯一 user_id:
SELECT COUNT(DISTINCT user_id)
FROM user_behavior
WHERE DATE(created_at) = '2024-06-15'
AND event_type IN ('login', 'page_view', 'submit_form');- 别只盯
login表——用户可能免登录刷首页,也算活跃 -
created_at字段必须是 MySQL 的DATETIME或TIMESTAMP,否则DATE()函数失效 - 如果用的是 PHP + PDO,注意绑定日期时用
'Y-m-d'格式,别传时间戳过去再转换
WAU要跨7天取并集,不是简单叠加7个DAU
WAU(周活跃用户)是最近7天内至少活跃过1天的独立用户数,本质是7个DAU集合的并集(UNION),不是 sum(DAU)。有人写成循环查7天再 array_unique() 合并,PHP 层处理百万级用户会内存爆掉。
推荐在数据库层一次性完成:
立即学习“PHP免费学习笔记(深入)”;
SELECT COUNT(DISTINCT user_id) FROM user_behavior WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 6 DAY);
-
INTERVAL 6 DAY是因为今天算第1天,往前推6天,共7天(含当天) - 确保
created_at有索引,否则全表扫描极慢 - 如果业务要求“自然周”(周一到周日),改用
WEEKDAY()或YEARWEEK()配合分组,别硬算日期范围
次日留存率的分母容易错成“当天注册用户”,其实该用“当天首次活跃用户”
留存率的核心陷阱:分母不是注册数,而是“当天第一次产生行为”的用户。比如用户A昨天注册但没操作,今天第一次打开App——他属于今天的“新活跃用户”,也应计入今日留存的分母。
计算次日留存(D1 Retention)需两步关联:
SELECT COUNT(DISTINCT t1.user_id) AS denominator, COUNT(DISTINCT t2.user_id) AS numerator, ROUND(COUNT(DISTINCT t2.user_id) / COUNT(DISTINCT t1.user_id) * 100, 2) AS retention_rate FROM ( SELECT user_id FROM user_behavior WHERE DATE(created_at) = '2024-06-15' GROUP BY user_id HAVING MIN(DATE(created_at)) = '2024-06-15' ) t1 LEFT JOIN user_behavior t2 ON t1.user_id = t2.user_id AND DATE(t2.created_at) = '2024-06-16';
-
HAVING MIN(DATE(created_at)) = ...是判断“首次活跃日”,比查register_time更准(注册≠活跃) - 如果用户当天没任何行为,
t2.user_id为 NULL,COUNT(DISTINCT t2.user_id)自动忽略,分子天然正确 - 千万避免在 PHP 里 for 循环每个用户查第二天是否存在记录——IO爆炸
PHP 实现时别用 date('Ymd') 拼接 SQL,小心时区和夏令时
很多代码用 date('Ymd') 生成日期字符串拼进 SQL,但服务器时区和数据库时区不一致时,凌晨行为可能被划到前一天或后一天。例如服务器设 Asia/Shanghai,MySQL 设 UTC,2024-06-15 00:00:00 +0800 在 UTC 是 2024-06-14 16:00:00,直接丢数据。
- 统一用 UTC 存储
created_at,查询时用CONVERT_TZ()转本地时间再截日期 - 或者 PHP 层用
new DateTime('now', new DateTimeZone('UTC'))生成时间,再 format 传入 - 更稳的做法:让 MySQL 自己算日期,PHP 只传时间戳或 ISO8601 字符串,由数据库函数处理时区
真正难的不是写对一两个 SQL,而是所有指标的时间口径必须严格对齐——活跃定义、去重逻辑、时区基准、空值处理,差一点,整个报表就失真。没人会盯着 SQL 看,但运营天天刷的数字,源头就在这里。











