GORM 的链式 API 覆盖了大部分场景,但复杂查询、数据库特有功能、性能优化时,原生 SQL 仍然是必要的。
执行原生查询:
var users []User
db.Raw("SELECT * FROM users WHERE age > ?", 20).Scan(&users)
Raw 方法执行 SQL,Scan 将结果映射到结构体。
执行不返回数据的 SQL:
db.Exec("UPDATE users SET status = ? WHERE age < ?", "minor", 18)
db.Exec("DELETE FROM users WHERE created_at < ?", time.Now().AddDate(-1, 0, 0))
使用占位符防止 SQL 注入:
db.Raw("SELECT * FROM users WHERE name = ? AND age > ?", "张三", 20).Scan(&users)
不同数据库的占位符:
?$1, $2, $3? 或 $1@p1, @p2GORM 会自动转换。
var user User
db.Raw("SELECT * FROM users WHERE id = ?", 1).Scan(&user)
查询单个值:
var name string
db.Raw("SELECT name FROM users WHERE id = ?", 1).Scan(&name)
var count int64
db.Raw("SELECT count(*) FROM users").Scan(&count)
var result map[string]interface{}
db.Raw("SELECT * FROM users WHERE id = ?", 1).Scan(&result)
var results []map[string]interface{}
db.Raw("SELECT * FROM users").Scan(&results)
使用命名参数提高可读性:
db.Raw("SELECT * FROM users WHERE name = @name AND age > @age",
sql.Named("name", "张三"),
sql.Named("age", 20),
).Scan(&users)
或者用 map:
db.Raw("SELECT * FROM users WHERE name = @name AND age > @age", map[string]interface{}{
"name": "张三",
"age": 20,
}).Scan(&users)
var users []User
db.Raw("SELECT * FROM users WHERE id IN (?)",
db.Model(&Order{}).Select("user_id").Where("amount > ?", 1000),
).Scan(&users)
db.Transaction(func(tx *gorm.DB) error {
if err := tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", 100, 1).Error; err != nil {
return err
}
if err := tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", 100, 2).Error; err != nil {
return err
}
return nil
})
大数据量时用 Rows 迭代:
rows, err := db.Raw("SELECT * FROM users").Rows()
if err != nil {
panic(err)
}
defer rows.Close()
for rows.Next() {
var user User
db.ScanRows(rows, &user)
// 处理 user
}
MySQL
db.Raw("SELECT * FROM users WHERE MATCH(name) AGAINST(?)", "张").Scan(&users)
db.Exec(`
INSERT INTO users (name, age)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE age = VALUES(age)
`, "张三", 25)
PostgreSQL
db.Raw("SELECT * FROM users WHERE name ILIKE ?", "%张%").Scan(&users)
db.Exec(`
INSERT INTO users (name, age)
VALUES ($1, $2)
ON CONFLICT (name) DO UPDATE SET age = $2
`, "张三", 25)
SQLite
db.Raw("SELECT * FROM users WHERE json_extract(profile, '$.age') > ?", 20).Scan(&users)
分组统计
type Stats struct {
Date string
Count int
Total float64
}
var stats []Stats
db.Raw(`
SELECT
DATE(created_at) as date,
COUNT(*) as count,
SUM(amount) as total
FROM orders
WHERE created_at >= ?
GROUP BY DATE(created_at)
ORDER BY date
`, time.Now().AddDate(0, -1, 0)).Scan(&stats)
多表关联
type UserOrder struct {
UserName string
OrderID uint
Amount float64
}
var results []UserOrder
db.Raw(`
SELECT u.name as user_name, o.id as order_id, o.amount
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.amount > ?
`, 1000).Scan(&results)
窗口函数
type RankResult struct {
ID uint
Name string
Score int
Rank int
}
var results []RankResult
db.Raw(`
SELECT id, name, score,
RANK() OVER (ORDER BY score DESC) as rank
FROM users
`).Scan(&results)
SQL 注入防护
始终使用参数绑定:
// 安全
db.Raw("SELECT * FROM users WHERE name = ?", input)
// 危险:SQL 注入风险
db.Raw(fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", input))
动态表名
表名不能参数化,需要验证:
func QueryTable(db *gorm.DB, table string) error {
allowed := map[string]bool{
"users": true,
"orders": true,
"products": true,
}
if !allowed[table] {
return errors.New("无效的表名")
}
var results []map[string]interface{}
return db.Raw(fmt.Sprintf("SELECT * FROM %s", table)).Scan(&results).Error
}
原生 SQL 是 GORM 的补充,适合复杂查询和数据库特有功能。注意 SQL 注入防护,始终使用参数绑定。