消息通知表需要存哪些核心字段
通知表不是日志表,不能只记“谁发了什么”。必须支持未读/已读状态、用户粒度的分发、点击跳转、批量标记,否则后期查 unread_count 或做推送补发会极其痛苦。
关键字段包括:
id、
user_id(接收者,非 sender_id)、
type(如
"order_paid"、
"system_announce")、
content(建议 JSON 格式存结构化内容,而非纯文本)、
link(跳转路径,如
"/orders/123")、
is_read(TINYINT(1),0/1)、
created_at、
expires_at(可选,避免垃圾数据堆积)。
不推荐把
sender_id或业务主键(如
order_id)直接当外键硬关联——业务表可能删记录,但通知要长期保留。可用
ref_type+
ref_id组合泛化引用,例如:
ref_type = "order",
ref_id = "123"。
索引怎么建才不拖慢查询
高频查询是「查某用户未读数」和「拉取最新 20 条」,这两类查询必须走索引,否则
SELECT COUNT(*) FROM notify WHERE user_id = 123 AND is_read = 0在百万级数据下会秒变慢 SQL。
ALTER TABLE `notify` ADD INDEX `idx_user_read` (`user_id`, `is_read`, `created_at`);
这个联合索引覆盖了三个常用过滤条件,且
created_at放最后可支持
ORDER BY created_at DESC LIMIT 20无需 filesort。
注意:别单独给
is_read建索引——区分度太低(95% 是 0),MySQL 优化器大概率不走;也别漏掉
user_id,它是查询的绝对前置条件。
如何避免重复插入相同通知
比如订单支付成功后,下游服务可能因重试触发多次通知逻辑,导致用户收到 3 条一模一样的「付款成功」提醒。
解决方法是加唯一约束:
UNIQUE KEY `uk_user_type_ref` (`user_id`, `type`, `ref_id`)。前提是业务上允许「同一用户对同一业务实体只收一条同类通知」——这在大多数场景成立(比如你不会想看到 5 条「你的订单 #123 已发货」)。
插入时用
INSERT IGNORE或
ON DUPLICATE KEY UPDATE is_read = VALUES(is_read),避免报错中断流程。
如果类型逻辑更复杂(如「7 天内只推一次优惠券」),就得在应用层用 Redis 记录
user:123:type:coupon:seen并设 TTL,数据库层无法优雅解决时间窗口去重。
要不要分表?什么时候开始拆
单表撑到 500 万行以内,只要索引合理、归档策略到位,完全不用分表。过早分片只会增加 join、count、后台管理的复杂度。
真正该拆的信号是:
SHOW PROCESSLIST里频繁出现
Copying to tmp table或
Creating sort index;或者每天新增超 10 万条,且
is_read = 0的比例长期低于 5%,说明冷数据太多。
优先考虑按时间归档:每月建一个
notify_202406表,用视图或应用路由写入;比按 user_id hash 分片更易维护、更利于物理删除旧数据。
另外,
content字段如果平均超 2KB,建议抽到单独的
notify_payload表,主表只留摘要,避免主索引膨胀拖慢所有查询。
