
本文介绍一种基于关系型数据库的轻量级通知系统设计,通过分离“通知模板”与“用户状态”两张表,避免消息冗余存储,同时支持全员广播、已读标记和高并发写入。
本文介绍一种基于关系型数据库的轻量级通知系统设计,通过分离“通知模板”与“用户状态”两张表,避免消息冗余存储,同时支持全员广播、已读标记和高并发写入。
在构建面向多用户的 Web 通知系统(如后台管理员向全体用户发送公告)时,一个常见误区是为每位用户单独插入一条完整通知记录——这不仅浪费存储空间,还会显著增加写入压力、降低查询效率,并给后续统计(如“未读数”)带来复杂性。正确的做法是采用模板化+状态映射的设计范式:将通知内容本身抽象为不可变的“模板”,而将用户个体行为(是否已读、何时阅读)作为独立的状态记录。
✅ 推荐数据库结构设计
建议创建两张表,职责清晰、无冗余:
-- 表1:notifications —— 存储通知元数据(一次写入,永久复用) CREATE TABLE notifications ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(255) NOT NULL, content TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, is_active TINYINT(1) DEFAULT 1 -- 支持软删除或停用历史通知 ); -- 表2:user_notifications —— 记录每个用户对该通知的状态(按需生成) CREATE TABLE user_notifications ( id BIGINT PRIMARY KEY AUTO_INCREMENT, notification_id INT NOT NULL, user_id INT NOT NULL, is_read TINYINT(1) DEFAULT 0, read_at DATETIME NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_notif (user_id, notification_id), -- 防止重复关联 FOREIGN KEY (notification_id) REFERENCES notifications(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE );
? 关键设计说明:
- notifications 表仅存一份原始消息,无论有多少用户,新增通知只需 1 次 INSERT;
- user_notifications 表在发送通知时批量插入(非实时逐条),例如使用 INSERT ... SELECT 将所有活跃用户 ID 与新通知 ID 关联;
- UNIQUE KEY uk_user_notif 确保同一用户对同一通知不会重复记录,规避逻辑错误。
? 发送全员通知的正确 SQL 实现(关键步骤)
当管理员发布一条新通知后,需将其“分发”给目标用户群体(如所有启用账户)。此时应执行原子性批量插入,而非循环 PHP 插入:
-- 步骤1:插入通知模板
INSERT INTO notifications (title, content)
VALUES ('系统维护提醒', '平台将于今晚23:00-24:00进行例行维护,请提前保存工作。');
-- 步骤2:获取刚插入的通知ID(假设为 LAST_INSERT_ID() = 123)
-- 步骤3:批量关联所有符合条件的用户(推荐使用事务保障一致性)
START TRANSACTION;
INSERT INTO user_notifications (notification_id, user_id)
SELECT 123, id
FROM users
WHERE status = 'active' AND deleted_at IS NULL;
COMMIT;✅ 该方式仅需 2 条 SQL 语句 + 1 次事务,即可完成“1 条消息 → N 个用户”的状态初始化,性能远超 N 次单条插入,且天然避免竞态与重复。
⚠️ 注意事项与最佳实践
- 不要在应用层循环插入:PHP 中 foreach ($users as $u) { INSERT ... } 是性能杀手,尤其在用户量达万级时极易触发连接超时或锁表;
- 合理使用索引:在 user_notifications(notification_id, is_read) 和 (user_id, is_read) 上建立复合索引,加速「某用户未读列表」和「某通知已读人数」两类高频查询;
- 延迟加载状态:首次访问通知中心时再生成 user_notifications 记录(即“懒分发”),适用于低频通知场景;但对强时效性通知(如紧急告警),建议同步批量写入;
- 清理策略:可定期归档或删除 is_read = 1 AND read_at
- 事务不是万能的:虽然 INSERT ... SELECT 本身具备原子性,但若业务含更复杂逻辑(如积分变动、日志记录),务必包裹 START TRANSACTION ... COMMIT/ROLLBACK。
✅ 总结
真正的可扩展通知系统不靠“复制消息”,而靠“解耦内容与状态”。通过 notifications + user_notifications 的二表结构,你既能保证消息源头唯一、存储零冗余,又能灵活支持已读/未读、批量标记、统计分析等全部功能。配合批量 SQL 与事务控制,即便面对数十万用户,也能在毫秒级完成全员触达——这才是现代 Web 应用应有的数据设计思维。

















