消息通知表核心字段包括:id、user_id、type、content(JSON格式)、link、is_read、created_at、expires_at,推荐用ref_type+ref_id泛化引用;索引需建(user_id, is_read, created_at)联合索引;唯一约束uk_user_type_ref防重复;500万行内无需分表,超量可按月归档。

消息通知表需要存哪些核心字段
通知表不是日志表,不能只记“谁发了什么”。必须支持未读/已读状态、用户粒度的分发、点击跳转、批量标记,否则后期查 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 表,主表只留摘要,避免主索引膨胀拖慢所有查询。










