Laravel Eloquent高效删除MySQL重复行 保留每组首条记录
大表重复数据删除高效实现
你当前使用的循环逐组查询删除方案属于典型的N+1查询场景,在千万级数据量下会产生成千上万次SQL请求,性能极差。以下两种方案均可单次定位所有待删除的重复记录ID,配合分块删除可以把执行时间从小时级压缩到分钟级。
重复判定规则保持不变:codes和customer_id字段值同时相同即为重复,每组保留ID最小的第一条记录。
方案一:MySQL 8.0+ 窗口函数方案(优先推荐)
基于ROW_NUMBER()窗口函数实现分组排序,直接标记每组内除首条(ID最小)之外的所有重复记录,没有多余关联查询,大表下性能最优。
// 构建待删除重复记录的查询,所有行号>1的都是需要删除的非首条记录 $duplicateList = Order::select('id') ->fromRaw('( SELECT id, ROW_NUMBER() OVER (PARTITION BY codes, customer_id ORDER BY id ASC) AS row_num FROM orders ) AS temp') ->where('row_num', '>', 1); // 分块删除,每次处理1000条,避免长事务、锁表和主从延迟 $duplicateList->chunkById(1000, function ($duplicates) { $deleteIds = $duplicates->pluck('id')->toArray(); Order::whereIn('id', $deleteIds)->delete(); });
方案二:MySQL 5.7 兼容方案
如果你的数据库版本低于8.0不支持窗口函数,使用自关联查询标记重复项,逻辑为:同codes+customer_id分组下,只要存在ID比当前记录更小的条目,当前记录就是需要删除的重复项。
$duplicateList = Order::selectRaw('DISTINCT t1.id') ->from('orders AS t1') ->join('orders AS t2', function ($join) { $join->on('t1.codes', '=', 't2.codes') ->on('t1.customer_id', '=', 't2.customer_id') ->on('t1.id', '>', 't2.id'); }); // 同样采用分块删除逻辑 $duplicateList->chunkById(1000, function ($duplicates) { $deleteIds = $duplicates->pluck('id')->toArray(); Order::whereIn('id', $deleteIds)->delete(); });
必做优化项
- 执行删除前先给两个判定字段加联合索引,查询性能可提升100倍以上:
ALTER TABLE orders ADD INDEX idx_codes_customer (codes, customer_id); - 分块大小根据自身数据库负载调整,建议控制在500-2000条区间,不要一次性删除超大量数据
- 正式执行前建议先查询待删除数据做备份,避免误删:
// 备份示例 // $backupData = $duplicateList->get(); // 存储到本地文件即可
内容的提问来源于stack exchange,提问作者Octoxan
相关产品推荐
相关产品推荐

