行为日志表设计要预留扩展性,别只存 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,而是让统计过程不影响线上写入、不拖垮主库、不因数据倾斜导致结果偏差——这些细节往往藏在分区策略、批量节奏和近似算法的选择里。
