用 GROUP BY + ORDER BY 可实现无需机器学习库的协同过滤推荐,核心是找相似用户并推荐其高频购买而目标用户未购的商品;需建联合索引、改 NOT IN 为 LEFT JOIN、分步处理稀疏行为,并为冷启动设计热门/新品兜底策略。

用 GROUP BY + ORDER BY 实现协同过滤雏形
不需要机器学习库,MySQL 本身就能跑出“买了这个的人也买了那些”的基础推荐。核心思路是:找出和目标用户行为相似的其他用户,再取他们买过但目标用户没买过的商品。
假设表结构为 orders(user_id, product_id, created_at),想给用户 123 推荐:
- 先查出用户 123 购买的全部
product_id - 再查所有购买过这些商品的其他用户(排除 123 自己)
- 统计这些“相似用户”购买频次最高的商品(且不在用户 123 的历史中)
SELECT product_id, COUNT(*) AS cnt
FROM orders
WHERE user_id IN (
SELECT DISTINCT user_id
FROM orders
WHERE product_id IN (
SELECT product_id FROM orders WHERE user_id = 123
) AND user_id != 123
)
AND product_id NOT IN (
SELECT product_id FROM orders WHERE user_id = 123
)
GROUP BY product_id
ORDER BY cnt DESC
LIMIT 5;
避免笛卡尔积导致查询爆炸
上面 SQL 在数据量稍大(比如相似用户超 5000 个)时会变慢,因为子查询嵌套+NOT IN容易触发全表扫描。真实项目中必须加索引,且改写为 LEFT JOIN 避免 NOT IN 对 NULL 敏感的问题。
- 强制建立联合索引:
ALTER TABLE orders ADD INDEX idx_uid_pid (user_id, product_id); - 把
NOT IN换成LEFT JOIN ... IS NULL,更稳定 - 如果用户行为稀疏(每人只买几个),先用临时表存“种子商品”,再分步关联,比单条 SQL 更可控
冷启动时 fallback 到热门或新品
新用户没任何购买记录,上面逻辑直接返回空。不能卡住,得有兜底策略:
- 按销量排序:
SELECT product_id FROM orders GROUP BY product_id ORDER BY COUNT(*) DESC LIMIT 5 - 按上架时间倒序(假设你有
products(created_at)表) - 混合策略:热门 × 0.7 + 7 天内新品 × 0.3,用
UNION ALL后加LIMIT控制总数
注意别在主查询里实时算热度权重,应预计算到缓存表或加字段(如 products.hot_score),否则每次推荐都扫全表。
为什么不用 JOIN 直接连三张 orders 表
有人尝试写三层 JOIN 模拟用户-商品-用户关系链,结果查几秒不出结果甚至 OOM。MySQL 的 JOIN 优化器对多层自连接非常不友好,尤其当 orders 表超百万行时,中间结果集极易膨胀。
- 用子查询明确切分步骤,让执行计划更可控
- 每步加
LIMIT(比如先取 top 100 相似用户)可大幅提速,牺牲一点精度换响应时间 - 真正上线前务必用
EXPLAIN FORMAT=TREE看是否走了索引,以及 rows 估算是否合理
推荐逻辑越简单,越要盯死执行计划和实际耗时——看起来一行 SQL 很轻量,背后可能扫千万行。










