如何在Laravel Scout中实现数据库变更时自动刷新索引?
因为你的数据库是由外部软件维护的,Laravel模型自带的saved/deleted这类事件根本触发不了——毕竟不是Laravel在操作数据嘛。下面给你几个实用的方案,按需选就行:
方案1:定时增量同步(最简单易实现)
如果对实时性要求不是特别高(比如允许几分钟延迟),这个方案最省心:
第一步:创建一个自定义Artisan命令,比如
scout:sync-updates
这个命令的核心逻辑:- 读取上次同步的时间(可以存在缓存、配置表甚至.env里)
- 查询目标表中
updated_at(或你用来标记修改时间的字段)大于上次同步时间的记录 - 对这些记录调用
$model->searchable()更新索引 - 处理删除:可以对比数据库主键和索引内的主键,把索引里存在但数据库已删除的条目调用
$model->unsearchable();如果外部软件用软删除,直接同步软删除记录即可 - 更新上次同步时间为当前时间
第二步:把命令加到定时任务
比如每分钟跑一次(根据数据量调整频率),可以用服务器Cron:* * * * * cd /path-to-your-laravel-project && php artisan scout:sync-updates >> /dev/null 2>&1也可以用Laravel自带的任务调度,在
app/Console/Kernel.php里定义:protected function schedule(Schedule $schedule) { $schedule->command('scout:sync-updates')->everyMinute(); }小提示:如果数据量很小,也可以直接用
php artisan scout:import全量同步,但增量同步效率更高。
方案2:SQL Server触发器+日志表(实时性更好)
如果需要接近实时的索引更新,可以用SQL Server触发器记录变更,再让Laravel处理:
第一步:创建变更日志表
比如叫model_changes,字段至少包含:id(主键)、model_type(多模型场景用)、record_id(变更记录的主键)、action(insert/update/delete)、created_at第二步:在目标业务表上创建触发器
以items表为例,写三个触发器分别处理INSERT、UPDATE、DELETE操作,举个INSERT触发器的SQL Server语法示例:CREATE TRIGGER trg_items_insert ON items AFTER INSERT AS BEGIN INSERT INTO model_changes (model_type, record_id, action, created_at) SELECT 'App\Models\Item', id, 'insert', GETDATE() FROM inserted; ENDUPDATE和DELETE触发器逻辑类似,注意用
deleted表获取删除的记录ID。第三步:Laravel处理日志
两种实现方式:- 定时任务:每隔几秒跑一次命令,读取
model_changes中未处理的记录,根据action对模型执行searchable()或unsearchable(),处理完标记为已读 - 队列异步处理:写一个处理单条日志的命令,用Laravel队列(比如Redis)异步执行,或者用外部工具监听日志表变化触发队列任务
- 定时任务:每隔几秒跑一次命令,读取
方案3:SQL Server变更数据捕获(CDC)或变更跟踪(Change Tracking)
如果你的SQL Server版本支持(SQL Server 2008+支持CDC,2016+支持更完善的Change Tracking),这是更专业的方案:
第一步:开启目标表的变更跟踪/CDC
以开启Change Tracking为例:-- 先开启数据库级别的变更跟踪 ALTER DATABASE YourDatabaseName SET CHANGE_TRACKING = ON (CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON); -- 开启表级别的变更跟踪 ALTER TABLE items ENABLE CHANGE_TRACKING WITH (TRACK_COLUMNS_UPDATED = ON);第二步:Laravel中查询变更记录
写自定义命令,用DB门面执行SQL获取变更记录ID:// 获取上次同步的版本号(初始为0) $lastVersion = Cache::get('scout_sync_version', 0); // 查询变更的记录 $changes = DB::select(" SELECT CT.ID FROM CHANGETABLE(CHANGES items, ?) AS CT ", [$lastVersion]); // 获取最新的版本号 $newVersion = DB::selectOne("SELECT CHANGE_TRACKING_CURRENT_VERSION() AS version")->version; // 处理变更记录 foreach ($changes as $change) { $item = \App\Models\Item::find($change->ID); if ($item) { $item->searchable(); } else { // 记录已删除,从索引移除 \App\Models\Item::where('id', $change->ID)->unsearchable(); } } // 更新版本号到缓存 Cache::put('scout_sync_version', $newVersion);第三步:将命令加入定时任务或队列,实现接近实时的同步
额外注意点
- TNTSearch的索引是文件型的,确保你用的
laravel-scout-tntsearch-driver版本支持增量更新(最新版本已支持) - 定时任务的频率要根据数据量调整,避免给数据库和服务器造成过大压力
- 处理物理删除的记录时,一定要通过主键对比或变更日志的方式,因为Laravel无法直接查询到已删除的模型实例
内容的提问来源于stack exchange,提问作者Francesco Gallo

