dau必须用count(distinct user_id),否则重复用户行为会导致虚高;postgresql中text型user_id过长会增加内存,clickhouse推荐uniq();日期截断需适配数据库,utc时间须转为asia/shanghai时区再截取。

DAU必须用COUNT(DISTINCT user_id),不能省略DISTINCT
直接COUNT(user_id)会把同一用户当天多次行为全算进去,结果虚高。比如用户A在2026-06-03有5次点击,COUNT(user_id)加5次,但DAU只该算1。真正有效的写法是:COUNT(DISTINCT user_id)。MySQL、PostgreSQL、ClickHouse都支持,但注意:PostgreSQL里user_id若为TEXT且超长,内存占用会上升;ClickHouse建议用uniq()替代,更省内存。
日期截断函数因数据库而异,别硬套DATE()
MySQL可用DATE(event_time),PostgreSQL得写event_time::date或CAST(event_time AS DATE),否则报错function date(timestamp without time zone) does not exist。如果event_time是UTC时间,而业务按北京时间(UTC+8)算日活,必须先转换时区:event_time AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Shanghai'再截日期,否则00:00–00:05的北京用户会被划到前一天。
MAU用自然月分组,但WHERE条件要能命中分区
大表上查MAU最怕全表扫描。如果表按月分区(如PARTITION BY RANGE (YEAR(event_time)*100 + MONTH(event_time))),WHERE里必须带明确的月份范围,例如:WHERE event_time >= '2026-05-01' AND event_time 。写成<code>WHERE YEAR(event_time) = 2026 AND MONTH(event_time) = 5会导致分区裁剪失效。另外,GROUP BY YEAR(event_time), MONTH(event_time)比GROUP BY DATE_FORMAT(event_time, '%Y-%m')更利于索引下推,尤其在MySQL中。
滚动MAU(如30天滚动)慎用关联子查询
想算“截至2026-06-03的滚动30天MAU”,常见错误是写关联子查询:(SELECT COUNT(DISTINCT user_id) FROM t WHERE event_time BETWEEN DATE_SUB('2026-06-03', INTERVAL 29 DAY) AND '2026-06-03')嵌在主查询里。这在数据量大时极慢,还容易OOM。更稳的解法是:先用CTE或临时表生成滚动窗口起止日期,再LEFT JOIN原表,最后按窗口聚合。ClickHouse可直接用arrayReduce('uniq', groupArray(user_id))配合date_trunc加速;PostgreSQL可借助generate_series和UNNEST构造日期序列,但要注意IN子句不支持去重逻辑,得改用JOIN。
COUNT(DISTINCT)内存翻倍,千万级用户建议提前评估是否启用HyperLogLog估算。











