gorm的Find跟Scan
笔者在使用gorm时通常都习惯用Find,不过最近因为在查找gorm的官网时意外看到Scan的用法,就好奇查了一下他们有什么不同,最后就整理出了这篇文章
Find和Scan在查询时gorm做了什么
Find
可以先看一下Find在查询资料时做了什么事情(下面的code可以不用看的太认真,看下面的结论比较重要,除非你真的很想知道非常底层的部分)
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB) {tx = db.getInstance()if len(conds) > 0 {if exprs := tx.Statement.BuildCondition(conds[0], conds[1:]...); len(exprs) > 0 {tx.Statement.AddClause(clause.Where{Exprs: exprs})}}tx.Statement.Dest = destreturn tx.callbacks.Query().Execute(tx)}
我们特别看下面这段就可以了解到,Find是在db进行Query执行前就进行结构的转换
tx.Statement.Dest = desttx.callbacks.Query().Execute(tx)
Scan
再来看看Scan在查询资料时做了什么事情(下面的code可以不用看的太认真,看下面的结论比较重要,除非你真的很想知道非常底层的部分)
func (db *DB) Scan(dest interface{}) (tx *DB) {config := *db.ConfigcurrentLogger, newLogger := config.Logger, logger.Recorder.New()config.Logger = newLoggertx = db.getInstance()tx.Config = &config // 请看这边~~if rows, err := tx.Rows(); err == nil {if rows.Next() {tx.ScanRows(rows, dest)} else {tx.RowsAffected = 0}tx.AddError(rows.Close())}currentLogger.Trace(tx.Statement.Context, newLogger.BeginAt, func() (string, int64) {return newLogger.SQL, tx.RowsAffected}, tx.Error)tx.Logger = currentLoggerreturn}// 上面的Scan里面的tx.Rows() 就会呼叫这边func (db *DB) Rows() (*sql.Rows, error) {tx := db.getInstance().Set("rows", true)tx = tx.callbacks.Row().Execute(tx)rows, ok := tx.Statement.Dest.(*sql.Rows)if !ok && tx.DryRun && tx.Error == nil {tx.Error = ErrDryRunModeUnsupported}return rows, tx.Error}// 上面的Scan里面的tx.ScanRows(rows, dest) 就会呼叫这边func (db *DB) ScanRows(rows *sql.Rows, dest interface{}) error {tx := db.getInstance()if err := tx.Statement.Parse(dest); !errors.Is(err, schema.ErrUnsupportedDataType) {tx.AddError(err)}tx.Statement.Dest = desttx.Statement.ReflectValue = reflect.ValueOf(dest)for tx.Statement.ReflectValue.Kind() == reflect.Ptr {elem := tx.Statement.ReflectValue.Elem()if !elem.IsValid() {elem = reflect.New(tx.Statement.ReflectValue.Type().Elem())tx.Statement.ReflectValue.Set(elem)}tx.Statement.ReflectValue = elem}Scan(rows, tx, ScanInitialized)return tx.Error}
我们特别看下面这两段就可以了解到,Scan是在db进行Query执行后才进行结构的转换
func (db *DB) Scan(dest interface{}) (tx *DB) { ... if rows, err := tx.Rows(); err == nil { if rows.Next() { tx.ScanRows(rows, dest) } else { tx.RowsAffected = 0 } tx.AddError(rows.Close()) } ... } func (db *DB) Rows() (*sql.Rows, error) { tx := db.getInstance().InstanceSet("rows", true) tx.callbacks.Row().Execute(tx) rows, ok := tx.Statement.Dest.(*sql.Rows) ... return rows, tx.Error } func (db *DB) ScanRows(rows *sql.Rows, dest interface{}) error { ... tx.Statement.Dest = dest tx.Statement.ReflectValue = reflect.ValueOf(dest) ... }
上述的差异会造成什么影响?
造成的差异为下
1.是Scan会需要透过Row()指定资料表的名称(是真的在db的资料表名称,users这种,不能用User)
2.Query可用的方法有差异可以在这个档案位置看到(gorm/callbacks/callbacks.go)
Find的Query:queryCallback := db.Callback().Query()
queryCallback.Register("gorm:query", Query)
queryCallback.Register("gorm:preload", Preload)
queryCallback.Register("gorm:after_query", AfterQuery)
Scan的Query:db.Callback().Row().Register("gorm:row", RowQuery)
db.Callback().Raw().Register("gorm:raw", RawExec)
3.虽然说scan在实行时要写sql语法比较麻烦,但相对的它指定搜寻特定栏位,也是有它的好处(避免select * 的状况)
4.Raw只能用在Scan不能用在Finddb.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result) V
db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Find(&result) X
结论
虽然Find可以让我们在写code的时候透过Preload或是Distinct之类的方法可以很轻鬆的帮我们做关联查询,但其实背后也牺牲了资料库查询的效能,如果遇到效能瓶颈时,或许把Find改成Row是一个不错的做法。
以上提供的解法为笔者的浅见。若以上内容有误,烦请各位读者用力指正,谢谢。