go的反射
-
IsZero() :
- 用于检查反射值是否为该类型的零值
- 适用于所有类型的反射值(Value)
- 对于数值类型,零值是0;对于字符串,是"";对于指针、接口、切片、map等,是nil;对于结构体,是所有字段都是零值的结构体
-
IsNil() :
- 专门用于检查指针、接口、切片、map、通道或函数类型的反射值是否为nil
- 如果对非这些类型的反射值调用IsNil()会panic
- 主要用于检查可空类型的nil状态
-
IsValid() :
- 检查反射值是否代表一个有效的值(非零Value)
- 如果反射值是通过非法方式获得的(如未初始化的接口或零值Value),会返回false
- 是所有反射值的基本有效性检查
- 有效值:能正确反映原始值的反射对象
- 无效值:无法对应任何实际值的反射对象(如零值Value或nil接口的反射)
使用案例
go
//根据库 "github.com/jinzhu/copier"学习而来
reflect.ValueOf(fromValue) //获取反射
from.Kind() //判断类型
例如
for from.Kind() == reflect.Ptr { //判断类型是否为指针
if from.IsNil() {
return nil // 源指针为nil则直接返回
}
from = from.Elem() //解引用 例如fromValue是个指针,直接打印是拿不到值,而是地址,而Elem()执行后获取对应的值
}
from.NumField() //获取结构体的总数量
for i := 0; i < from.NumField(); i++ {
fromField := from.NumField(i) //s获取结构体下面属性的反射
// 判断类型是否能安全赋值 a->b
if fromField.Type().AssignableTo(toField.Type()) {
toField.Set(fromField)
} else {
// 处理类型转换(如int到bool等)
if fromField.Kind() == reflect.Int16 && toField.Kind() == reflect.Bool{
toField.SetBool(fromField.Int() == 1)
}
}
}
goN+1的问题
go
var categoryList []model.CategoryModel
var count int64
config.Database.
Scopes(
sql.ConditionQueryCategory(body),
sql.Paginate(body.PageRequest),
).
Where("create_time BETWEEN ? AND ?", body.StartTime, body.EndTime).
Find(&categoryList).
Count(&count)
var respList []response.CategoryResponse
//性能问题
for _, v := range categoryList {
var resp response.CategoryResponse
copier.Copy(&resp, &v)
config.Database.
Model(&model.ArticleModel{}).
Where("category_id = ?", resp.ID).
Count(&resp.ArticleCount)
respList = append(respList, resp)
}
utils.ResponseSuccessData(app, map[string]any{
"page": respList,
"total": count,
})
上面的查询存在n+1的性能问题,总体时延600ms
同样的逻辑在 java的mybatis-plus上时延迟100ms左右
原因
1. 网络往返开销
- 每次查询都需要:建立连接 → 发送请求 → 等待响应 → 解析结果
- 假设网络延迟 10ms,10 次额外查询 = 100ms 纯等待
2. 数据库执行开销
-
每次 COUNT() 查询都需要:
- 解析 SQL
- 查询优化
- 执行计划生成
- 索引/表扫描
-
10 次 COUNT 查询 = 10 次完整执行流程
3. 连接池压力
- 高并发场景下,大量连接被占用
- 可能导致连接池耗尽,新请求阻塞
4. 索引失效风险
- 单次 COUNT 能用索引,但多次查询无法利用批量优化
- 数据库无法整体优化查询计划
永远不要在循环中执行数据库查询!
分页查询下只返回10条总数据问题
go
db := config.Database.
Table("t_category as c").
Select("c.*, COUNT(a.id) as article_count").
Joins("LEFT JOIN t_article a ON c.id = a.category_id").
Where("c.create_time BETWEEN ? AND ?", body.StartTime, body.EndTime).
Group("c.id, c.category_name").
Scopes(
sql.ConditionQueryCategory(body),
sql.Paginate(body.PageRequest),
)
db.Count(&count)
db.
Find(&categoryList)
该查询中无论如何只有10条,原因在于分页条件提前,生成的两条sql一条的查count带limit10,查列表也有limit10,需求为列表有limit10,而count不需要
正确解法
go
//归纳总体查询条件,否则会出现条件查出来有2条但总的有30来条的情况
db := config.Database.
Table("t_category as c").
Select("c.*, COUNT(a.id) as article_count").
Joins("LEFT JOIN t_article a ON c.id = a.category_id").
Where("c.create_time BETWEEN ? AND ?", body.StartTime, body.EndTime).
Group("c.id, c.category_name").
Scopes(sql.ConditionQueryCategory(body))
db.Count(&count) //先聚合总数
db.
Scopes(sql.Paginate(body.PageRequest)). //在进行分页查询
Find(&categoryList)