广告

MySQL 实现点餐系统订单提醒功能的完整指南

1. 数据库设计与字段规划

在实现点餐系统的订单提醒功能时,数据库设计是基础。核心对象是“订单”和“提醒记录”,需要以高效的方式对齐业务流程。关键字段包括 order_iduser_idrestaurant_idstatusorder_timeestimated_ready_timereminder_sent_atreminder_type 等等。通过设计清晰的外键与索引,可以确保提醒触发时查询成本可控。

为了便于扩展与回放历史,建议额外设计一个 提醒日志 表,用于记录每一次提醒的发送时间、渠道和结果状态,避免重复发送并支持审计。可审计性幂等性是生产环境的关键维度。

1.1 表结构概览

下面给出一个简化的表结构示例,便于理解字段含义和索引布局。请确保在生产中根据实际字段命名与数据类型调整


CREATE TABLE orders (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  user_id BIGINT NOT NULL,
  restaurant_id BIGINT NOT NULL,
  status ENUM('received','preparing','ready','completed','cancelled') NOT NULL DEFAULT 'received',
  order_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  estimated_ready_time DATETIME,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

补充一个用于记录提醒的表结构,便于后续统计与去重


CREATE TABLE order_reminders (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  order_id BIGINT NOT NULL,
  reminder_type ENUM('preparation','ready','pickup') NOT NULL,
  scheduled_at DATETIME NOT NULL,
  sent_at DATETIME,
  status ENUM('pending','sent','failed') NOT NULL DEFAULT 'pending',
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

还需要一个通知表,用于追踪发送结果及用户可见的消息内容。


CREATE TABLE notifications (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  user_id BIGINT NOT NULL,
  order_id BIGINT,
  type VARCHAR(50),
  message TEXT,
  delivered_at DATETIME,
  status ENUM('pending','delivered','failed') NOT NULL DEFAULT 'pending',
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

索引设计要点:在 orders 上对 estimated_ready_timestatusid建立组合索引,以便快速定位即将到达或需要提醒的订单;在 order_reminders 上对 order_idstatus 的组合建立索引,提升定时任务的查询效率。

2. 触发时机与业务规则

设计清晰的业务规则是保证提醒准确、不过度打扰用户的关键。核心触发点包括:订单从准备阶段进入就绪阶段、以及按预计完成时间进行的提前提醒。节流策略也同样重要:同一订单在一定时间内不重复发送、多渠道并发发送的幂等处理等。通过规则化的状态机,可以稳定地落地提醒逻辑。

建议将提醒分为多类:准备就绪提醒即将取餐提醒、以及可选的 进度跟踪提醒。不同类型的提醒应有独立的 scheduled_time 计算与单独的记录路径,确保可追溯性与可扩展性。

2.1 业务规则要点

以下是常见的规则要点,便于后续实现与维护:提前时间段(如 10 分钟前、5 分钟前)用于触发提醒;幂等性确保同一提醒不会重复发送;渠道优先级(如推送优先于短信)可以提升用户体验;错误回滚策略用于处理发送失败的情况并支持重发。

3. 使用 MySQL 定时任务实现提醒

MySQL 自带的事件调度器(Event Scheduler)可以在数据库层面定期执行任务,适合简单、稳定的提醒触发场景。需要注意的是,事件调度器需要开启,并且应与应用层的通知逻辑协作,以实现真正的消息投放。

通过事件可以将就绪提醒、取餐提醒等逻辑统一到一个定时任务里,减少应用层轮询压力,并且便于进行历史统计与故障排查。

3.1 使用事件调度实现定时提醒

以下示例展示一个简化的事件调度实现:定时检查状态为 pending 且预计就绪时间在未来 10 分钟内的订单,生成通知并标记提醒为已发送。


-- 启用事件调度
SET GLOBAL event_scheduler = ON;

-- 创建定时任务(请在支持 DELIMITER 的环境中执行)
DELIMITER $$
CREATE EVENT IF NOT EXISTS ev_order_reminders
ON SCHEDULE EVERY 5 MINUTE
DO
BEGIN
  -- 1) 生成通知记录
  INSERT INTO notifications (user_id, order_id, type, message, created_at, status)
  SELECT o.user_id, o.id, 'reminder',
         CONCAT('您的订单号 ', o.id, ' 将在 ', DATE_FORMAT(o.estimated_ready_time, '%H:%i'), ' 完成,请留意。'),
         NOW(), 'pending'
  FROM orders o
  JOIN order_reminders r ON r.order_id = o.id
  WHERE r.status = 'pending'
    AND o.estimated_ready_time BETWEEN NOW() AND NOW() + INTERVAL 10 MINUTE;

  -- 2) 标记相关提醒为已发送
  UPDATE order_reminders r
  JOIN orders o ON o.id = r.order_id
  SET r.status = 'sent', r.sent_at = NOW()
  WHERE r.status = 'pending'
    AND o.estimated_ready_time BETWEEN NOW() AND NOW() + INTERVAL 10 MINUTE;
END$$
DELIMITER ;

注意事项:在生产环境中,应将事件的执行时间间隔与实际业务峰值对齐;同时,事件体内应尽量避免长事务,避免锁表或长时间占用资源。

4. 通过应用层发送通知

尽管数据库层可以生成提醒,但实际的发送落地通常需要应用层的能力:接入短信、推送、邮件等通道、实现重试策略、以及实现跨渠道的幂等保护。应用层应定期查询待发送的通知记录,完成发送后回写状态。可观测性与幂等性是核心设计

下面给出一个简化的 Python 示例,用于轮询待投递的通知并通过外部推送服务发送,成功则标记为已投递;失败可按配置进行重试或转入人工处理。


import requests
import pymysql

# 数据库连接(请按实际环境替换参数)
conn = pymysql.connect(host='db_host', user='user', password='pwd', db='db_name', charset='utf8mb4')

def fetch_due_notifications(limit=100):
    with conn.cursor(pymysql.cursors.DictCursor) as cursor:
        cursor.execute("""
            SELECT n.id, n.user_id, n.order_id, n.type, n.message
            FROM notifications n
            WHERE n.status = 'pending' AND n.delivered_at IS NULL
            ORDER BY n.created_at ASC
            LIMIT %s
        """, (limit,))
        return cursor.fetchall()

def mark_delivered(notif_id):
    with conn.cursor() as cursor:
        cursor.execute("""
            UPDATE notifications
            SET status = 'delivered', delivered_at = NOW()
            WHERE id = %s
        """, (notif_id,))
    conn.commit()

def send_push(user_id, message):
    # 这里示例接入外部推送服务
    resp = requests.post("https://api.push.example/send", json={"uid": user_id, "msg": message})
    return resp.status_code == 200

def main():
    notifs = fetch_due_notifications()
    for n in notifs:
        ok = send_push(n['user_id'], n['message'])
        if ok:
            mark_delivered(n['id'])
        else:
            # 可实现重试策略或将失败记录到单独表
            pass

if __name__ == "__main__":
    main()

幂等性实现要点:确保同一通知在网络波动或重试后不会重复投放,可以通过在通知表中增加唯一性标识、或在发送端对同一订单的同一类型通知进行去重处理。

5. 数据库表设计与完整建表示例

为便于落地部署,下面给出整套建表脚本的综合示例,便于直接在 MySQL 实例中执行。请结合实际业务字段与命名规范调整


CREATE TABLE orders (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  user_id BIGINT NOT NULL,
  restaurant_id BIGINT NOT NULL,
  status ENUM('received','preparing','ready','completed','cancelled') NOT NULL DEFAULT 'received',
  order_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  estimated_ready_time DATETIME,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE order_reminders (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  order_id BIGINT NOT NULL,
  reminder_type ENUM('preparation','ready','pickup') NOT NULL,
  scheduled_at DATETIME NOT NULL,
  sent_at DATETIME,
  status ENUM('pending','sent','failed') NOT NULL DEFAULT 'pending',
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE notifications (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  user_id BIGINT NOT NULL,
  order_id BIGINT,
  type VARCHAR(50),
  message TEXT,
  delivered_at DATETIME,
  status ENUM('pending','delivered','failed') NOT NULL DEFAULT 'pending',
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE INDEX idx_orders_status_est_time ON orders (status, estimated_ready_time);
CREATE INDEX idx_reminders_order_status ON order_reminders (order_id, status);

6. 监控与调试

监控是确保提醒功能稳定落地的重要环节。核心指标包括:未投递通知的积压数量、定时任务执行耗时、订单状态转换与提醒之间的时延、通道发送成功率等。应将这些指标接入日志与指标系统,便于快速定位瓶颈。

可使用以下 SQL 片段快速自检常见问题:未投递通知的比例历史提醒的重复率、以及最近 24 小时的提醒分布。


-- 查最近 24 小时的未投递通知
SELECT COUNT(*) AS pending_count
FROM notifications
WHERE delivered_at IS NULL
  AND created_at >= NOW() - INTERVAL 1 DAY;

-- 查最近 24 小时的提醒记录
SELECT reminder_type, COUNT(*) AS cnt
FROM order_reminders
WHERE created_at >= NOW() - INTERVAL 1 DAY
GROUP BY reminder_type;

7. 性能与安全考虑

在高并发场景下,性能与安全尤为重要。要点包含:对关键查询建立覆盖索引、避免长事务导致的锁争用、合理的批量处理与分页策略;对提醒内容的敏感字段进行最小化暴露、确保传输过程中使用加密、以及对外部通知服务的访问做限流。对于历史数据,可以定期归档到冷存储,降低主库压力。

此外,数据保留策略应符合业务与合规要求;可设置提醒与通知的清理策略,例如按月清理超过一定时间且状态为已完成的记录,确保数据库维度与存储成本可控。

广告

数据库标签