重复查询需从逻辑层、缓存、关联加载三方面根治,distinct()仅掩盖症状;tp5/tp6对distinct()支持差异大,易报错;group()适用于分组聚合场景,php层去重仅为临时补救。

重复查询不是“加个 distinct(true) 就完事”的问题,它本质是逻辑层误触发、缓存缺失或关联加载失控导致的。直接在 SQL 层去重(比如 distinct)只能掩盖症状,治标不治本。
为什么 distinct() 经常没用甚至报错
ThinkPHP 的 distinct() 是纯 SQL 级去重,只对最终结果集生效,不解决“同一条数据被查了 5 次”这种根本问题。而且跨版本行为差异大:
- TP5 中
$model->distinct()->select()可能隐式用主键去重,但不可靠,也不推荐依赖 - TP6 强制要求显式指定字段:
$model->distinct(true)->field('name')->select(),漏掉field()直接抛异常 - 在
join查询里用distinct(true)+ 多字段(如field('a.id, a.name, b.title')),只要任意一列值不同,整行就不算重复——根本达不到“按某字段去重”的效果 -
order()和limit()链式调用位置会影响结果:TP 默认把limit放最外层,而distinct在内层,导致去重前的数据顺序不可控,order()->limit(1)加distinct()可能返回非预期的那条
真正有效的重复查询拦截点
重复查询大多发生在循环中查关联、事件多次触发、或未开启查询缓存的场景。优先从执行源头控制:
- 查关联数据别写
foreach ($users as $u) { User::get($u['id'])->profile; },改用with('profile')一次预加载 - 模型方法里避免在
getAttr或访问器中发起新查询,这类逻辑容易被模板反复调用 - 开启查询缓存:
Db::table('user')->cache(true)->where('status', 1)->select(),注意缓存 key 默认是 SQL 字符串,相同条件会命中;若需自定义 key,用cache('user_status_1', 3600) - 全局监听
sql_explain事件或使用think-sql-debug中间件,在日志里 grep 重复 SQL 片段(比如连续出现 3 次SELECT * FROM user WHERE id = 123)
group() 比 distinct() 更可控的去重场景
当你要“每个用户取最新一条订单”,distinct 完全无能为力,必须用 group 配合聚合函数:
<pre class="brush:php;toolbar:false;">$sub = Order::field('user_id, max(create_time) as latest_time')
->group('user_id');
$orders = Order::alias('o')
->join([$sub => 'sub'], 'o.user_id = sub.user_id AND o.create_time = sub.latest_time')
->select();
注意两点:
group('user_id') 单独用在多字段查询里,MySQL 5.7+ 严格模式下会报错(<code>ONLY_FULL_GROUP_BY),必须所有select字段都在group列表中,或用聚合函数包裹- 不要依赖
group来“随便去重”,它本质是分组聚合,语义上不是去重工具;真要按某字段唯一取一行,闭包子查询 + join 是目前 TP 生态最稳的写法
数组层去重只是临时补救
如果数据库层已无法修改(比如第三方 SDK 返回的原始结果),才考虑 PHP 层后处理:
-
array_unique($list, SORT_REGULAR)可用于对象数组去重,但注意它比较的是整个结构,性能随数据量下降明显 - 更轻量的做法是用
array_column()提取关键字段(如id),再用array_keys(array_flip())去重索引,最后array_intersect_key()拿回原数组对应项 - 永远别在控制器里写
foreach ($data as &$item) { $item['xxx'] = Db::name('log')->where('id', $item['id'])->find(); }—— 这种代码哪怕加了array_unique也救不回来
真正难处理的重复查询,往往藏在模型事件、中间件或自动关联加载里,而不是明面上的 SQL 写法。打开调试模式看 SQL 日志,比翻文档更快定位问题。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











