gorm的Find跟Scan

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不能用在Find
db.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是一个不错的做法。

以上提供的解法为笔者的浅见。若以上内容有误,烦请各位读者用力指正,谢谢。


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章