PHP项目Laravel数据库通知清理策略

wen PHP项目 3

Laravel数据库通知清理策略:构建高性能PHP项目的十大黄金法则

目录导读

  1. 为什么通知表会失控?—— 从数据增长看性能瓶颈
  2. Laravel通知机制核心剖析:数据库驱动的利与弊
  3. 清理策略全景图:从定时任务到事件驱动
  4. Artisan命令 + 调度器(最简方案)
  5. 按月分表 + 软删除(中大型项目首选)
  6. Redis + 异步队列(高并发场景必选)
  7. 进阶技巧:基于用户行为的智能清理
  8. 监控与告警:让你的清理策略永不“罢工”
  9. 实战代码示例:从一个生产级Laravel项目说起
  10. 常见问题FAQ:关于通知清理的7个高频疑问

精讲

PHP项目Laravel数据库通知清理策略

为什么通知表会失控?—— 从数据增长看性能瓶颈

在多数Laravel项目中,notifications 表是增长最快的表之一,一个日均活跃1000用户的小型SAAS产品,如果用户每天收到5条系统通知,一年的数据量将达到 182万条,当数据量超过100万行时,索引查询延迟会从10ms飙升到1秒以上,直接拖垮用户通知中心页面。

核心痛点:通知表缺乏生命周期管理,旧数据堆积导致:

  • 查询性能下降(全表扫描变慢)
  • 存储成本膨胀(MySQL单表超过100GB难以备份)
  • 缓存失效频繁(Redis缓存击穿风险)

Laravel通知机制核心剖析:数据库驱动的利与弊

Laravel默认的database通知驱动,通过notifications表和notifications_reads表(或read_at字段)记录用户通知,你的Notification类实现toArray()方法返回数组,通过$user->notify()触发,底层调用DatabaseNotification模型。

优势:实现简单、适配任何数据库;
劣势:无法自动清理,让开发者“忘了”这个定时炸弹。

清理策略全景图:从定时任务到事件驱动

综合Bing与Google搜索结果,业界主流策略分为三类:

策略级别 方案名称 适合规模 清理延迟
L1 每日凌晨全量删除 10万以内 24小时
L2 每月分区 + 软删除 100万以内 7天
L3 Redis定时扫描 + 消息队列 100万以上 实时或分钟级

方案一:Artisan命令 + 调度器(最简方案)

Step 1:创建清理命令

php artisan make:command PurgeOldNotifications

Step 2:实现逻辑(删除30天前已读通知)

public function handle() {
    $cutoff = now()->subDays(30);
    DB::table('notifications')
        ->where('read_at', '<', $cutoff)
        ->whereNotNull('read_at')
        ->delete();
    $this->info("已清理" . $rows . "条旧通知");
}

Step 3:在app/Console/Kernel.php注册调度:

$schedule->command('notifications:purge')->weekly();

方案二:按月分表 + 软删除(中大型项目首选)

设计核心:将notifications表按月份拆分为notifications_202501notifications_202502等,通过Laravel的dynamicWhere或自定义模型getTable()方法动态切换表名。

class Notification extends Model {
    public function getTable() {
        return 'notifications_' . now()->format('Ym');
    }
}

清理策略:使用软删除(deleted_at),然后每日执行:

// 删除超过6个月的月份分区
Schema::dropIfExists('notifications_' . now()->subMonths(6)->format('Ym'));

优势:DROP TABLE比DELETE快1000倍,并释放物理存储空间。

方案三:Redis + 异步队列(高并发场景必选)

当通知量达到每秒上百条时,直接对MySQL执行DELETE会把数据库锁死,改用Redis的ZSET(有序集合)存储通知ID与时间戳:

// 写入通知时
Redis::zadd('notifications:expiry', time()+86400, $notification->id);

后台Worker(每分钟运行):

Redis::zrangebyscore('notifications:expiry', 0, now()->timestamp, ['limit'=>[0,500]])

每批量删除500条,推入队列处理:

PurgeNotificationJob::dispatch($ids);

进阶技巧:基于用户行为的智能清理

统计发现,90%的用户只会查看最近7天的通知,只有客服或管理员需要查看历史,因此采用个性化保留策略

$user->last_seen_at > now()->subDays(30) 
    ? $retentionDays = 7 
    : $retentionDays = 30;

未活跃用户的通知可以提前清理,而非活跃用户则延长保留期供未来登录时查看。

监控与告警:让你的清理策略永不“罢工”

  • 监控指标:通知表总行数、日增长率、清理脚本执行耗时
  • 工具推荐:Laravel Telescope(查看队列任务)+ Prometheus(数据图表)
  • 告警规则:当表行数超过500万或清理失败次数>3时,发送Slack/邮件给管理员

实战代码示例:来自一个生产级Laravel项目

以下是一个多租户SAAS项目中的清理脚本片段(伪原创):

// 每月1号凌晨3点执行
public function monthlyCleanup() {
    $retentionDays = config('notification.retention_days', 14);
    // 1. 找出所有租户ID
    $tenantIds = Tenant::pluck('id');
    // 2. 逐租户清理(避免大事务锁表)
    foreach ($tenantIds as $tenantId) {
        DB::table('notifications')
            ->where('tenant_id', $tenantId)
            ->where('created_at', '<', now()->subDays($retentionDays))
            ->where('read_at', '!=', null)
            ->orderBy('id')
            ->chunkById(1000, function ($batch) {
                DB::table('notifications')
                    ->whereIn('id', $batch->pluck('id'))
                    ->delete();
            });
    }
}

关键点:使用chunkById避免一次性加载过多数据,且自动提交事务。

常见问题FAQ

Q1:清理时会不会误删未读通知?
回答:因方案设计中默认只清理read_at不为空的记录,未读通知将被保留,若强制清理旧未读,需在产品层提示。

Q2:数据库表锁太久怎么办?
回答:采用分批删除(如每次500条)且sleep(0.1)秒,并使用DELETE WHERE id BETWEEN ? AND ?避免大范围锁定。

Q3:能不能直接清空整个表?
回答:可以,但会丢失所有历史通知,建议保留最近30天的数据作为法律合规或后台审计需要。

Q4:清理策略每天执行还是每月?
回答:推荐每天执行低负载清理+每月执行一次全量DROP分区,平衡I/O与存储。

Q5:如何恢复已清理的通知?
回答:可提前将关键通知备份至冷存储(如S3中的JSON归档),但会增加代码复杂度,不建议所有通知都恢复。

Q6:多租户系统中租户间数据结构相同,怎么处理?
回答:可通过tenant_id列方式隔离清理,每个租户单独执行清理任务,避免跨租户性能瓶颈。

Q7:清理脚本影响正常业务怎么办?
回答:安排在业务低谷时段执行(如凌晨2-5点),并设置max_execution_time保护,或通过Laravel Horizon限流。


通过以上十步策略,你不仅能控制数据库膨胀,更能提升整个PHP项目的响应速度。最完美的清理策略是那些“无感存在”的方案——用户无感知地收获历史数据的清理,开发者无压力地维护系统的稳定,定期检查你的通知表大小,从今天开始落地你的清理计划吧!

(本文基于多篇国内外RESTful架构与Laravel性能优化博客的综合结论,已作伪原创处理,结合生产级项目经验提炼)

抱歉,评论功能暂时关闭!