行为日志表设计需兼顾扩展性、写入性能与查询效率:字段应包含event_type、page_url等;高频写入宜用批量插入或消息队列;按created_at分区并优化聚簇索引;UV统计优先用HyperLogLog或预计算。

行为日志表设计要预留扩展性,别只存 user_id 和 action
直接用 user_id + action + created_at 三字段建表,短期看着够用,但很快会卡在「用户从哪个页面点击的?用了什么设备?是否登录态?」这类问题上。实际项目里建议至少包含:event_type(如 'click'/'view'/'submit')、page_url、ua_hash(或 device_type)、session_id、referral。不存完整 UA 是为了避免索引膨胀和隐私风险。
高频写入场景下,避免直接 INSERT INTO behavior_log
用户行为日志写入量大、并发高,如果每点一下就同步落库,MySQL 容易成为瓶颈。常见做法是客户端或网关层做轻量聚合(比如 1 秒内同用户同事件只记 1 次),再批量写入;或者走消息队列(Kafka → Flink/Logstash → MySQL)。若必须直写,务必:
- 使用
INSERT INTO ... VALUES (...), (...), (...)批量插入,单次不超过 1000 行 - 关闭
autocommit,显式用BEGIN/COMMIT包裹批次 - 表引擎选
InnoDB,但innodb_buffer_pool_size要调高,避免频繁刷脏页
按天分区 + 聚簇索引优化,否则 count(*) 会越来越慢
行为日志表数据增长快,不做分区,单表超千万后 COUNT(*) 或 GROUP BY DATE(created_at) 就明显变慢。推荐按 created_at 做 RANGE 分区(如每月一分区),并确保主键或第一个索引列为 (created_at, user_id) 这类组合——让数据物理存储尽量按时间局部聚集。注意:
- MySQL 8.0+ 才支持对非主键列自动分区裁剪,旧版本务必把分区键放进查询条件
-
ALTER TABLE ... REORGANIZE PARTITION操作会锁表,得避开高峰 - 不要给
user_id单独建高频INDEX,容易拖慢写入;真要查某用户全量行为,走WHERE created_at >= ? AND user_id = ?利用聚簇索引即可
统计 UV 时,别直接 COUNT(DISTINCT user_id)
在大表上跑 COUNT(DISTINCT user_id),尤其是跨多月数据时,很容易触发临时表磁盘 spill,OOM 或超时。生产环境更稳妥的做法:
- 用 HyperLogLog:MySQL 8.0+ 可配合
HLL_INIT()/HLL_ADD()/HLL_CARDINALITY()(需启用hll插件) - 预计算:每天凌晨跑定时任务,把当日去重
user_id存进汇总表(如daily_uv_summary(date, hll_data)),查询时只合并几天的 HLL 值 - 实在没条件,至少加
WHERE created_at BETWEEN ? AND ?并确保该范围能命中分区和索引,避免全表扫描
COUNT(DISTINCT) 在千万级日志表上查一周 UV,基本等于主动触发慢查询告警。
真正难的不是写出统计 SQL,而是让统计过程不影响线上写入、不拖垮主库、不因数据倾斜导致结果偏差——这些细节往往藏在分区策略、批量节奏和近似算法的选择里。










