在 Laravel 项目开发中,批量更新数据库记录是高频操作,很多场景下我们需要准确知道本次批量更新实际影响了多少行数据,比如用于操作日志统计、权限变更校验等。但不少开发者发现直接使用常规的更新方法返回的结果和预期不符,这就需要了解 Laravel 更新操作的返回值逻辑。

Laravel 常规更新方法的返回值逻辑
Laravel 的 Eloquent 模型和查询构造器都提供了 update 方法,但是两者的返回值含义有区别。我们先看基础的代码示例:
<?php
// 使用查询构造器批量更新
$affectedCount = DB::table('users')
->where('status', 0)
->update(['status' => 1]);
// 使用 Eloquent 模型批量更新
$affectedCount = User::where('status', 0)->update(['status' => 1]);
<?php
这里返回的 $affectedCount 其实就是数据库层面返回的影响行数,但是为什么很多开发者会觉得不准确呢?常见的原因是更新的值和原有值完全一致时,数据库不会真正执行行修改,此时返回的影响行数为 0,而开发者可能期望只要匹配到条件就算影响行数。
影响行数不符合预期的常见场景
场景一:更新值与原有值完全一致
比如用户表中 id 为 1 的记录 status 已经是 1,此时执行更新 status 为 1 的操作,数据库不会修改该行,返回的影响行数就是 0,而不是 1。
场景二:使用批量赋值忽略时间戳
如果更新时没有触发模型的 updated_at 时间戳自动更新,比如使用查询构造器而非 Eloquent 的 update 方法,或者手动关闭了时间戳功能,那么只要匹配到条件,即使更新值和原有值一致,部分数据库驱动也可能返回匹配行数,这和数据库本身的实现有关。
准确获取批量更新影响行数的方案
方案一:直接使用查询构造器的 update 返回值
如果你的需求是获取数据库实际修改的行数,那么直接使用 DB 门面或者 Eloquent 的 update 方法返回值即可,这是最准确的方式,示例代码如下:
<?php
// 准确获取实际修改的行数
$realAffected = DB::table('orders')
->where('user_id', 10)
->where('pay_status', 0)
->update(['pay_status' => 1, 'pay_time' => now()]);
echo "实际修改了 {$realAffected} 条订单记录";
<?php
方案二:需要获取匹配行数而非实际修改行数
如果你的业务需求是只要匹配到更新条件的记录都算入影响行数,不管值是否变化,那么可以先查询匹配的行数,再执行更新操作:
<?php
// 先查询匹配条件的行数
$matchedCount = DB::table('articles')
->where('category_id', 5)
->whereNull('deleted_at')
->count();
// 执行批量更新
DB::table('articles')
->where('category_id', 5)
->whereNull('deleted_at')
->update(['category_id' => 6]);
echo "匹配到 {$matchedCount} 条记录,已执行更新操作";
<?php
方案三:使用原生 SQL 获取影响行数
如果需要更灵活的控制,也可以使用原生 SQL 语句执行更新,然后获取影响行数,示例代码如下:
<?php
// 原生 SQL 批量更新
$affected = DB::update(
'UPDATE products SET stock = stock - 1 WHERE stock > 0 AND id IN (1,2,3)'
);
echo "库存扣减影响了 {$affected} 条商品记录";
<?php
注意事项
- 不同数据库(MySQL、PostgreSQL、SQLite)对于更新值和原有值一致时的返回行数逻辑可能有差异,开发时需要根据使用的数据库类型做验证。
- 如果使用了数据库的乐观锁或者触发器,可能会影响影响行数的返回结果,需要结合具体业务逻辑判断。
- Eloquent 的批量更新不会触发模型的
saving、saved等事件,如果需要事件支持,需要遍历模型逐个更新,此时影响行数就是遍历更新的次数。
总结来说,Laravel 中获取批量更新影响行数的核心是明确业务需求是需要实际修改的行数还是匹配条件的行数,根据实际需求选择对应的方案即可,大部分场景下直接使用 update 方法的返回值就能满足需求。