一、GORM 原生 SQL 核心方法梳理
1. 四大原生 SQL 执行 API
表格
| 方法 | 用途 | 返回值 | 适用场景 |
|---|---|---|---|
Raw(sql, args...).Scan(&dest) |
查询单 / 多条数据(SELECT) | Rows、填充结构体 / 切片 | 复杂联表、自定义聚合、复杂子查询 |
Exec(sql, args...) |
执行 DML/DDL(INSERT/UPDATE/DELETE/CREATE) | Result(RowsAffected、Error) | 增删改、建索引、建表、存储过程 |
Row(sql, args...) |
查询单行单行数据(标准 database/sql.Row) | *sql.Row | 只查 1 行简单数据,手动 Scan |
Rows(sql, args...) |
查询多行原始行(标准 database/sql.Rows) | *sql.Rows | 超大结果集流式读取,手动遍历 |
2. 关键安全要点(必看)
- 禁止字符串拼接 SQL ,一律用
?占位符传参,防止 SQL 注入 - Raw/Scan 映射规则:数据库字段名和结构体字段名大小写不敏感,下划线自动匹配驼峰
- 执行 DDL 语句(建表、索引、truncate)统一使用
Exec() - 事务内原生 SQL 同样使用
tx.Raw()/tx.Exec()
3. 常用配套能力
- 命名参数
db.Raw("SELECT * FROM users WHERE name=@name", sql.Named("name", "张三")) - 获取影响行数
result.RowsAffected - 获取自增 ID(插入后)
db.Raw("LAST_INSERT_ID()").Scan(&id) - 事务中执行原生 SQL
- 原生 SQL + GORM 链式条件拼接(
db.Model(&User{}).Where("age>18").Raw(...))
4. Raw Scan 映射规则
- 结构体映射:字段名和 sql 查询字段对应
- 单值接收:
var name string; db.Raw("select name from user where id=?",1).Scan(&name) - 切片接收多条:
var list []User; db.Raw("select * from user").Scan(&list) - map 接收单行:
var m map[string]interface{} - \[\] map 接收多行:
var listMap []map[string]interface{}
二、完整可运行示例代码(整合所有原生 SQL 场景)
package main
import (
"database/sql"
"errors"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
)
// User 测试模型
type User struct {
gorm.Model
Name string `gorm:"size:32"`
Age int `gorm:"comment:年龄"`
Email string `gorm:"size:64;unique"`
Score float64 `gorm:"comment:分数"`
}
var db *gorm.DB
// 初始化数据库
func initDB() {
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm_demo?charset=utf8mb4&parseTime=True&loc=Local"
var err error
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // 打印原生SQL
})
if err != nil {
log.Fatal("连接失败:", err)
}
// 自动建表
db.AutoMigrate(&User{})
fmt.Println("数据库初始化完成\n")
}
// 1. Raw + Scan 查询:多条映射结构体
func rawQueryStruct() {
fmt.Println("===== 1.Raw查询多条映射结构体 =====")
var users []User
// ? 占位符防注入
sqlStr := "SELECT id,name,age,score FROM users WHERE age > ? AND score > ?"
err := db.Raw(sqlStr, 18, 80).Scan(&users).Error
if err != nil {
log.Println("查询失败:", err)
return
}
for _, u := range users {
fmt.Printf("id:%d name:%s age:%d score:%.1f\n", u.ID, u.Name, u.Age, u.Score)
}
}
// 2. Raw 查询单行、单个字段
func rawQuerySingleValue() {
fmt.Println("\n===== 2.Raw查询单行单个字段 =====")
var name string
var age int
// 查询单行多字段
err := db.Raw("SELECT name,age FROM users WHERE id = ?", 1).Scan(&name, &age).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
fmt.Println("无数据")
return
}
log.Println(err)
}
fmt.Printf("name:%s age:%d\n", name, age)
// 只查单个数值
var count int64
db.Raw("SELECT COUNT(*) FROM users").Scan(&count)
fmt.Printf("用户总数:%d\n", count)
}
// 3. Raw 查询 map / []map 动态接收(无结构体)
func rawQueryMap() {
fmt.Println("\n===== 3.Raw查询map动态接收 =====")
// 单行 map
var rowMap map[string]interface{}
db.Raw("SELECT * FROM users WHERE id = ?", 2).Scan(&rowMap)
fmt.Println("单行map数据:", rowMap)
// 多行 []map
var listMap []map[string]interface{}
db.Raw("SELECT name,score FROM users WHERE score > ?", 85).Scan(&listMap)
fmt.Println("多行map列表:", listMap)
}
// 4. 命名参数 sql.Named
func rawNamedParam() {
fmt.Println("\n===== 4.命名参数查询 =====")
var u User
sqlStr := "SELECT * FROM users WHERE name=@uname AND age=@uage"
err := db.Raw(sqlStr,
sql.Named("uname", "张三"),
sql.Named("uage", 20),
).Scan(&u).Error
if err == nil {
fmt.Printf("命名参数查询结果:%+v\n", u)
}
}
// 5. Row() 单行原始查询(标准sql.Row)
func sqlRowDemo() {
fmt.Println("\n===== 5.Row()单行原生查询 =====")
row := db.Raw("SELECT name,score FROM users WHERE id=?", 3).Row()
var name string
var score float64
err := row.Scan(&name, &score)
if err != nil {
log.Println(err)
return
}
fmt.Printf("Row查询:%s %.1f\n", name, score)
}
// 6. Rows() 多行流式读取(大数据推荐)
func sqlRowsStream() {
fmt.Println("\n===== 6.Rows()流式读取大数据 =====")
rows, err := db.Raw("SELECT id,name,age FROM users").Rows()
if err != nil {
log.Println(err)
return
}
defer rows.Close() // 必须关闭,释放连接
for rows.Next() {
var id uint
var name string
var age int
if err := rows.Scan(&id, &name, &age); err != nil {
log.Println(err)
break
}
fmt.Printf("流式行 id:%d name:%s\n", id, name)
}
}
// 7. Exec 执行DML:INSERT/UPDATE/DELETE
func execDML() {
fmt.Println("\n===== 7.Exec执行增删改原生SQL =====")
// 插入
insertSql := "INSERT INTO users(name,age,email,score) VALUES(?,?,?,?)"
resInsert := db.Exec(insertSql, "原生SQL测试", 26, "raw@demo.com", 91.2)
fmt.Printf("插入影响行数:%d\n", resInsert.RowsAffected)
// 获取自增主键ID
var newID uint
db.Raw("SELECT LAST_INSERT_ID()").Scan(&newID)
fmt.Printf("新增用户ID:%d\n", newID)
// 更新
updateSql := "UPDATE users SET score=? WHERE name=?"
resUpdate := db.Exec(updateSql, 99, "原生SQL测试")
fmt.Printf("更新影响行数:%d\n", resUpdate.RowsAffected)
// 删除
delSql := "DELETE FROM users WHERE name=?"
resDel := db.Exec(delSql, "原生SQL测试")
fmt.Printf("删除影响行数:%d\n", resDel.RowsAffected)
}
// 8. Exec 执行DDL语句(建索引、TRUNCATE)
func execDDL() {
fmt.Println("\n===== 8.Exec执行DDL原生SQL =====")
// 创建索引
idxSql := "CREATE INDEX idx_user_name ON users(name)"
err := db.Exec(idxSql).Error
if err != nil {
fmt.Println("索引已存在或创建失败:", err)
} else {
fmt.Println("创建name索引成功")
}
// 清空表(慎用)
// db.Exec("TRUNCATE TABLE users")
}
// 9. 事务中执行原生SQL
func rawInTx() {
fmt.Println("\n===== 9.事务内执行原生SQL =====")
tx := db.Begin()
if tx.Error != nil {
log.Fatal(tx.Error)
}
// 事务插入
tx.Exec("INSERT INTO users(name,age,email,score) VALUES(?,?,?,?)", "事务原生", 22, "txraw@demo.com", 88)
// 事务更新
tx.Exec("UPDATE users SET age=? WHERE name=?", 23, "张三")
// 模拟异常回滚
// err := errors.New("手动回滚测试")
// if err != nil {
// tx.Rollback()
// fmt.Println("事务回滚")
// return
// }
tx.Commit()
fmt.Println("事务原生SQL执行提交成功")
}
// 10. GORM链式条件 + Raw混合使用
func rawWithWhereChain() {
fmt.Println("\n===== 10.链式Where拼接 + Raw =====")
var list []User
// 先用Where过滤,再执行自定义Raw
db.Model(&User{}).Where("age > ?", 18).
Raw("SELECT id,name,score FROM users WHERE age > ? AND score > 80", 18).
Scan(&list)
fmt.Println("混合查询结果:", list)
}
func main() {
initDB()
// 查询类原生SQL
rawQueryStruct()
rawQuerySingleValue()
rawQueryMap()
rawNamedParam()
sqlRowDemo()
sqlRowsStream()
// DML/DDL执行
execDML()
execDDL()
// 事务原生SQL
rawInTx()
// 混合链式调用
rawWithWhereChain()
fmt.Println("\n===== 所有原生SQL示例执行完毕 =====")
}
三、补充高频使用场景总结
场景 1:联表复杂查询(Raw 首选)
type UserJoin struct {
UserName string
Score float64
}
var data []UserJoin
sql := `
SELECT u.name user_name, u.score
FROM users u
LEFT JOIN xxx_table t ON u.id = t.user_id
WHERE u.age > ?
`
db.Raw(sql, 18).Scan(&data)
场景 2:分页原生 SQL
sql
SELECT * FROM users WHERE age>? LIMIT ?,?
pageNum := 1
pageSize := 2
offset := (pageNum - 1) * pageSize
var list []User
db.Raw("SELECT * FROM users WHERE age>? LIMIT ?,?", 18, offset, pageSize).Scan(&list)
场景 3:获取执行错误与影响行数
res := db.Exec("UPDATE users SET age=?", 20)
if res.Error != nil {
log.Fatal(res.Error)
}
fmt.Println("影响行数:", res.RowsAffected)
场景 4:禁止 SQL 注入错误示范(不要写)
// 错误!字符串拼接,注入风险
name := "张三';drop table users;"
db.Raw(fmt.Sprintf("select * from users where name='%s'", name))
四、依赖安装
go get gorm.io/gorm
go get gorm.io/driver/mysql
五、代码覆盖能力清单
- Raw 查询映射结构体、单值、map、切片 map
- 占位符参数、命名参数 Named
- Row 单行查询、Rows 流式多行读取(大数据)
- Exec 执行 INSERT/UPDATE/DELETE DML
- Exec 执行 CREATE INDEX / TRUNCATE DDL
- 事务中执行原生 SQL
- GORM Where 链式 + Raw 混合使用
- 获取自增 ID、总条数、受影响行数
- 防 SQL 注入标准写法
- 错误处理、行连接延迟关闭 defer rows.Close ()