GORM 子查询有意义,但不是银弹——它的价值取决于具体场景。
什么时候有意义
场景 例子 为什么用子查询
IN/EXISTS 过滤 查询"有订单的客户" WHERE id IN (SELECT user_id FROM orders) 比先查 orders 再查 users 少一次往返
聚合后过滤 查询"平均订单金额 > 1000 的用户" 聚合必须发生在子查询里,外层再做比较
关联更新/删除 删除"30天未登录的用户" DELETE FROM users WHERE id IN (SELECT user_id FROM logs WHERE...)
避免 N+1(特定情况) 只需要关联表的一个聚合值 子查询比 Preload + 遍历更省内存和查询次数
什么时候没必要或有害
场景 问题
简单 JOIN 能搞定 子查询可读性差,优化器可能生成更差的执行计划
只需要关联表的明细数据 用 Preload/Joins 更自然,GORM 封装更好
大数据量 相关子查询(Correlated Subquery)性能可能极差,MySQL 尤其明显
复杂业务逻辑 子查询嵌套深了难以维护,不如拆两步或用 CTE(GORM 也支持)
GORM 中的写法示例
// 1. 子查询作为 IN 条件
subQuery := db.Model(&Order{}).Select("user_id").Where("amount > ?", 1000)
db.Where("id IN (?)", subQuery).Find(&users)
// 2. 子查询作为 EXISTS
db.Where("EXISTS (?)", db.Model(&Order{}).Select("1").Where("orders.user_id = users.id")).Find(&users)
// 3. 子查询作为字段(标量子查询)
db.Select("*, (SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id) as order_count").Find(&users)
// 4. FROM 子查询(派生表)
subQuery := db.Model(&Order{}).Select("user_id, SUM(amount) as total").Group("user_id")
db.Table("(?) as u", subQuery).Where("total > ?", 5000).Find(&results)
核心建议
1. 先写 JOIN,性能不够再考虑子查询——GORM 的 Joins 在多数场景更直观
2. 用 Explain 验证——同样的逻辑,MySQL/PostgreSQL 的执行计划差异很大
3. 避免多层嵌套子查询——超过两层考虑拆成多次查询或用 CTE(WITH 子句,GORM v2 支持)
4. 注意 GORM 的 Session(&gorm.Session{})——子查询构建时要注意作用域,避免条件泄漏
一句话总结:子查询在 GORM 里是有意义的工具,但属于"需要时才用"的优化手段,不是默认选择。如果你能用 Joins 或 Preload 清晰表达,通常优先用它们。