本文介绍使用 laravel query builder 或 eloquent 实现一键清理:仅删除那些邮箱重复且未分配任何角色(即在 `role_user` 表中无对应记录)的 user 记录,保留每组重复邮箱中至少一个活跃用户(推荐保留最新或最旧的),并避免误删已授权用户。
在 Laravel 应用中,因注册逻辑缺陷或数据迁移问题,常出现同一邮箱对应多个 users 记录,而其中部分用户未被分配任何角色(即 role_user 表中无 user_id 关联)。这类“孤儿用户”不仅占用数据
库资源,还可能干扰权限校验与统计分析。本文提供生产环境友好的解决方案——精准定位并删除“既是重复邮箱、又无任何角色”的用户记录,同时规避常见陷阱(如全量误删、未考虑软删除、忽略大小写差异等)。
目标需同时满足两个条件:
⚠️ 注意:原始答案中的子查询 havingRaw('count(*) > 0') 存在逻辑错误(应为 > 1 才表示重复),且未处理邮箱大小写敏感问题(如 John@ExAmple.com 和 john@example.com 应视为重复)。以下为修正后的健壮实现:
use Illuminate\Support\Facades\DB;
use App\Models\User;
// 删除所有「邮箱重复」且「无角色」的用户(保留每组重复邮箱中 id 最小的记录,即最早创建者)
User::whereNotIn('id', function ($query) {
// 获取所有有角色的用户 ID
$query->select('user_id')
->from('role_user')
->whereNotNull('user_id');
})
->whereIn('email', function ($query) {
// 获取所有重复邮箱(统一转小写去重)
$query->select(DB::raw('LOWER(email) as email'))
->from('users')
->whereNull('deleted_at') // 仅统计有效用户
->groupBy(DB::raw('LOWER(email)'))
->havingRaw('COUNT(*) > 1');
})
->whereNotIn('id', function ($query) {
// 保留每组重复邮箱中 id 最小的用户(防止整组被删光)
$query->select(DB::raw('MIN(id)'))
->from('users as u2')
->whereColumn('u2.email', 'users.email')
->whereNull('u2.deleted_at')
->groupBy(DB::raw('LOWER(u2.email)'));
})
->delete();若需更高性能或调试方便,可改用原生风格:
DB::table('users')
->whereNotIn('id', function ($q) {
$q->select('user_id')->from('role_user')->whereNotNull('user_id');
})
->whereIn(DB::raw('LOWER(email)'), function ($q) {
$q->select(DB::raw('LOWER(email)'))
->from('users')
->whereNull('deleted_at')
->groupBy(DB::raw('LOWER(email)'))
->havingRaw('COUNT(*) > 1');
})
->whereNotIn('id', function ($q) {
$q->select(DB::raw('MIN(id)'))
->from('users as u2')
->whereColumn('u2.email', 'users.email')
->whereNull('u2.deleted_at')
->groupBy(DB::raw('LOWER(u2.email)'));
})
->delete();DB::transaction(function () {
// 上述 delete() 调用
});ALTER TABLE role_user ADD INDEX idx_user_id (user_id); ALTER TABLE users ADD INDEX idx_lower_email ((LOWER(email)));
$dangling = User::whereNotIn('id', function($q) {
$q->select('user_id')->from('role_user');
})->whereIn('email', function($q) {
$q->select('email')->from('users')->groupBy('email')->havingRaw('COUNT(*) > 1');
})->get();通过上述方法,你可在一行核心逻辑中安全、高效地完成数据净化,既符合 Laravel 最佳实践,又兼顾可读性与可维护性。