GORM 与原生 SQL 对比

用 GORM 还是写原生 SQL?这个问题没有标准答案。理解两者的差异,才能在合适的场景做合适的选择。

代码对比

同样的功能,看看两种写法的区别。

创建记录

原生 SQL:

func createUser(db *sql.DB, name, email string, age int) (int64, error) {
    result, err := db.Exec(
        "INSERT INTO users (name, email, age, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
        name, email, age, time.Now(), time.Now(),
    )
    if err != nil {
        return 0, err
    }
    return result.LastInsertId()
}

GORM:

func createUser(db *gorm.DB, name, email string, age int) (uint, error) {
    user := User{Name: name, Email: email, Age: age}
    if err := db.Create(&user).Error; err != nil {
        return 0, err
    }
    return user.ID, nil
}

查询记录

原生 SQL:

func getUser(db *sql.DB, id int64) (*User, error) {
    var user User
    err := db.QueryRow(
        "SELECT id, name, email, age, created_at, updated_at FROM users WHERE id = ?",
        id,
    ).Scan(&user.ID, &user.Name, &user.Email, &user.Age, &user.CreatedAt, &user.UpdatedAt)
    if err != nil {
        return nil, err
    }
    return &user, nil
}

GORM:

func getUser(db *gorm.DB, id uint) (*User, error) {
    var user User
    if err := db.First(&user, id).Error; err != nil {
        return nil, err
    }
    return &user, nil
}

复杂查询

原生 SQL:

func searchUsers(db *sql.DB, minAge int, keyword string) ([]User, error) {
    query := `SELECT id, name, email, age, created_at, updated_at 
              FROM users 
              WHERE age >= ? AND name LIKE ? 
              ORDER BY created_at DESC 
              LIMIT 100`
    
    rows, err := db.Query(query, minAge, "%"+keyword+"%")
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    var users []User
    for rows.Next() {
        var user User
        if err := rows.Scan(&user.ID, &user.Name, &user.Email, &user.Age, &user.CreatedAt, &user.UpdatedAt); err != nil {
            return nil, err
        }
        users = append(users, user)
    }
    return users, nil
}

GORM:

func searchUsers(db *gorm.DB, minAge int, keyword string) ([]User, error) {
    var users []User
    err := db.Where("age >= ?", minAge).
        Where("name LIKE ?", "%"+keyword+"%").
        Order("created_at desc").
        Limit(100).
        Find(&users).Error
    return users, err
}

GORM 的优势

开发效率

GORM 代码更简洁,特别是字段多的表。原生 SQL 的 Scan 要一个个字段写,加字段时两边都要改。

类型安全

GORM 把结果自动映射到结构体,编译期就能发现类型问题。原生 SQL 的字符串容易写错,运行时才报错。

数据库无关

换数据库只改连接配置,查询代码不用动。原生 SQL 可能要改语法,比如 MySQL 的反引号、PostgreSQL 的双引号。

内置功能

  • 软删除
  • 时间戳自动更新
  • 钩子函数
  • 关联关系
  • 事务封装

这些用原生 SQL 都要自己实现。

原生 SQL 的优势

性能

ORM 有抽象层开销。对于高频查询,这点开销可能很关键。

灵活性

复杂查询、子查询、窗口函数,原生 SQL 想怎么写怎么写。GORM 虽然支持原生 SQL,但混用会失去 ORM 的优势。

可控性

你清楚知道执行了什么 SQL。GORM 有时候生成的 SQL 不够优化,需要看日志排查。

学习成本

SQL 是通用技能,ORM 是特定框架。新人接手项目,懂 SQL 的比懂特定 ORM 的多。

性能对比

简单测试:

// GORM
func BenchmarkGORMCreate(b *testing.B) {
    for i := 0; i < b.N; i++ {
        user := User{Name: "test", Email: "test@test.com"}
        db.Create(&user)
    }
}

// 原生 SQL
func BenchmarkSQLCreate(b *testing.B) {
    for i := 0; i < b.N; i++ {
        db.Exec("INSERT INTO users (name, email, created_at) VALUES (?, ?, ?)",
            "test", "test@test.com", time.Now())
    }
}

结果大致是原生 SQL 快 20-30%。但实际项目中,数据库 I/O 才是瓶颈,这点差距往往可以忽略。

混合使用

GORM 支持原生 SQL,可以在需要时切换:

// GORM 风格
db.Where("age > ?", 18).Find(&users)

// 原生 SQL
db.Raw("SELECT * FROM users WHERE age > ?", 18).Scan(&users)

// 执行原生 SQL
db.Exec("UPDATE users SET status = ? WHERE age < ?", "active", 18)

建议:日常 CRUD 用 GORM,复杂查询用原生 SQL。

选择建议

用 GORM 的场景

  • 中小型项目
  • CRUD 为主
  • 团队 SQL 水平一般
  • 需要快速迭代
  • 需要支持多种数据库

用原生 SQL 的场景

  • 高性能要求
  • 复杂查询多
  • 数据库特性依赖强
  • 团队 SQL 水平高
  • 已有成熟的 SQL 封装

实际项目中的做法

很多项目是混合使用的:

// 简单查询用 GORM
func GetUser(id uint) (*User, error) {
    var user User
    err := db.First(&user, id).Error
    return &user, err
}

// 复杂报表用原生 SQL
func GetMonthlyReport(year, month int) ([]Report, error) {
    var reports []Report
    err := db.Raw(`
        SELECT 
            DATE(created_at) as date,
            COUNT(*) as count,
            SUM(amount) as total
        FROM orders
        WHERE YEAR(created_at) = ? AND MONTH(created_at) = ?
        GROUP BY DATE(created_at)
        ORDER BY date
    `, year, month).Scan(&reports).Error
    return reports, err
}

小结

GORM 和原生 SQL 不是对立的,各有适用场景:

方面GORM原生 SQL
开发效率
性能略低
灵活性
学习成本需要学习框架只需懂 SQL
类型安全编译期检查运行时检查

实际项目中,建议用 GORM 处理常规操作,复杂场景下用原生 SQL。关键是理解两者的边界,不要强行用 ORM 处理不适合的场景。