mgo和mongo-go-driver使用心得比較

mgo和mongo-go-driver比較

庫介紹

  • mgo:是MongoDB的Go語言驅動,它用基於Go語法的簡單API實現了豐富的特性,並經過良好測試。使用起來很順手,文檔足夠,前期一直在使用,可惜是不維護了;

  • mongo-go-driver:官方的驅動,設計的很底層,從mgo轉來的時候不是很順手,主要是使用事務;

用法介紹

連接數據庫

  • mgo

    import ( 
    "gopkg.in/mgo.v2" 
    "gopkg.in/mgo.v2/bson" 
    )
    session, err := mgo.Dial("127.0.0.1:27017")
  • mongo-go-driver

    import (
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    )
    
    client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017"))

兩者在數據庫的連接上都很簡單,後者使用了options,可以設置連接數,連接時間,socket時間,超時時間等;

索引

  • mgo

    func createUniqueIndex(collection string, keys ...string) {
        ms, c := Connect(setting.DatabaseSetting.DBName, collection)
        defer ms.Close()
    
        // 設置統計表唯一索引
        index := mgo.Index{
            Key:        keys, // 索引鍵
            Unique:     true, // 唯一索引
            DropDups:   true, // 存在數據後創建, 則自動刪除重複數據
            Background: true, // 不長時間佔用寫鎖
        }
        // 創建索引
        err := c.EnsureIndex(index)
        if err != nil {
            Logger.Error("EnsureIndex error", zap.String("error", err.Error()))
        }
    }
  • mongo-go-driver

    func createUniqueIndex(collection string, keys ...string) {
        db := DB.Mongo.Database(setting.DatabaseSetting.DBName).Collection(collection)
        opts := options.CreateIndexes().SetMaxTime(10 * time.Second)
    
        indexView := db.Indexes()
        keysDoc := bsonx.Doc{}
        
        // 複合索引
        for _, key := range keys {
            if strings.HasPrefix(key, "-") {
                keysDoc = keysDoc.Append(strings.TrimLeft(key, "-"), bsonx.Int32(-1))
            } else {
                keysDoc = keysDoc.Append(key, bsonx.Int32(1))
            }
        }
    
        // 創建索引
        result, err := indexView.CreateOne(
            context.Background(),
            mongo.IndexModel{
                Keys:    keysDoc,
                Options: options.Index().SetUnique(true),
            },
            opts,
        )
        if result == "" || err != nil {
            Logger.Error("EnsureIndex error", zap.String("error", err.Error()))
        }
    }

mgo可以直接構建複合索引,按順序傳入多個參數就可以createIndex(addrTrxC, "address_id", "asset_id"),但是mongo-go-driver,需要自己做一下處理

查詢

  • mgo

    func FindProNode() ([]DBNode, error) {
        var nodes []DBNode
        ms, c := Connect(setting.DatabaseSetting.DBName, superNodeC)
        defer ms.Close()
        err := c.Find(M{}).Sort("-vote_count").Limit(10).All(&nodes)
        if err != nil {
            return nil, err
        }
    
        return nodes, nil
    }
  • mongo-go-driver

    func FindNodes() ([]DBNode, error) {
        var nodes []DBNode
    
        c := Connect(setting.DatabaseSetting.DBName, superNodeC)
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()
    
        opts := options.Find().SetSort(bsonx.Doc{{"vote_count", bsonx.Int32(-1)}})
        cursor, err := c.Find(ctx, M{}, opts)
        if err != nil {
            return nil, err
        }
    
        for cursor.Next(context.Background()) {
            var node DBNode
            if err = cursor.Decode(&node); err != nil {
                return nil, err
            } else {
                nodes = append(nodes, node)
            }
        }
    
        return nodes, nil
    }

在查詢單個元素的時候,兩個驅動都都方便,但是,當查詢多個元素的時候,mongo-go-driver不能直接解析到數組,需要藉助cursor這個類型,遍歷解析所有的數據(麻煩,需要自己封裝)。

插入

  • mgo

    // 通用
    func Insert(db, collection string, docs ...interface{}) error {
        ms, c := Connect(db, collection)
        defer ms.Close()
        return c.Insert(docs...)
    }
    
    //插入
    func InsertNode(node DBNode) error {
        err := Insert(setting.DatabaseSetting.DBName, superNodeC, node)
        return err
    }
  • mongo-go-driver

    func Insert(db, collection string, docs ...interface{}) (*mongo.InsertManyResult, error) {
        c := Connect(db, collection)
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()
    
        return c.InsertMany(ctx, docs)
    }
    
    
    func InsertNode(node DBNode) error {
        _, err := Insert(setting.DatabaseSetting.DBName, superNodeC, node)
        return err
    }

插入的區別也不是很大

修改

  • mgo

    func UpsertNode(node DBNode) (*mgo.ChangeInfo, error) {
        findM := M{"pub_key": node.PubKey}
    
        ms, c := Connect(setting.DatabaseSetting.DBName, superNodeC)
        defer ms.Close()
    
        updateM := bson.M{"$inc": M{"vote_count": node.VoteCount},
            "$set": M{
                "name":            node.Name,
                "address":         node.Address,
                "first_timestamp": node.FirstTimestamp}}
        return c.Upsert(findM, updateM)
    }
  • mongo-go-driver

    func Update(db, collection string, query, update interface{}) (*mongo.UpdateResult, error) {
        c := Connect(db, collection)
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()
        
        opts := options.Update().SetUpsert(true)
        return c.UpdateOne(ctx, query, update,opts)
    }

兩者都可以更新一個mgo(update) mongo-go-driver(updateOne),和更新多個mgo(updateAll) mongo-go-driver(updateMany);但是在upsert的時候,mgo直接使用upsert就可以,mongo-go-driver需要設置opts := options.Update().SetUpsert(true)

刪除

  • mgo

    func Remove(db, collection string, query interface{}) error {
        ms, c := Connect(db, collection)
        defer ms.Close()
        return c.Remove(query)
    }
    
    func RemoveNode(pubKey string) error {
        findM := M{"pub_key": pubKey}
        err := Remove(setting.DatabaseSetting.DBName, superNodeC, findM)
        return err
    }
  • mongo-go-driver

    func Remove(db, collection string, query interface{}) (*mongo.DeleteResult, error) {
        c := Connect(db, collection)
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()
        return c.DeleteOne(ctx, query)
    }
    
    
    func RemoveNode(pubKey string) error {
        findM := M{"pub_key": pubKey}
        _, err := Remove(setting.DatabaseSetting.DBName, superNodeC, findM)
        return err
    }
    

刪除也比較簡單,大致相同

事務

  • mgo
    不支持
  • mongo-go-driver

    • 需要使用SessionContext
    • 所有的修改之前需要查詢的,請都使用SessionContext(即都使用事務)

      因爲多處使用,所以封裝了一個方法;
      在這個方法中需要實現的方法是Exec的operator
      
      type DBTransaction struct {
          Commit func(mongo.SessionContext) error
          Run    func(mongo.SessionContext, func(mongo.SessionContext, DBTransaction) error) error
          Logger *logging.Logger
      }
      
      func NewDBTransaction(logger *logging.Logger) *DBTransaction {
          var dbTransaction = &DBTransaction{}
          dbTransaction.SetLogger(logger)
          dbTransaction.SetRun()
          dbTransaction.SetCommit()
          return dbTransaction
      }
      
      func (d *DBTransaction) SetCommit() {
          d.Commit = func(sctx mongo.SessionContext) error {
              err := sctx.CommitTransaction(sctx)
              switch e := err.(type) {
              case nil:
                  d.Logger.Info("Transaction committed.")
                  return nil
              default:
                  d.Logger.Error("Error during commit...")
                  return e
              }
          }
      }
      
      func (d *DBTransaction) SetRun() {
          d.Run = func(sctx mongo.SessionContext, txnFn func(mongo.SessionContext, DBTransaction) error) error {
              err := txnFn(sctx, *d) // Performs transaction.
              if err == nil {
                  return nil
              }
              d.Logger.Error("Transaction aborted. Caught exception during transaction.",
                  zap.String("error", err.Error()))
      
              return err
          }
      }
      
      func (d *DBTransaction) SetLogger(logger *logging.Logger) {
          d.Logger = logger
      }
      
      func (d *DBTransaction) Exec(mongoClient *mongo.Client, operator func(mongo.SessionContext, DBTransaction) error) error {
          ctx, cancel := context.WithTimeout(context.Background(), 20*time.Minute)
          defer cancel()
      
          return mongoClient.UseSessionWithOptions(
              ctx, options.Session().SetDefaultReadPreference(readpref.Primary()),
              func(sctx mongo.SessionContext) error {
                  return d.Run(sctx, operator)
              },
          )
      }
      
      
      
      //具體調用
      func SyncBlockData(node models.DBNode) error {
          dbTransaction := db_session_service.NewDBTransaction(Logger)
      
          // Updates two collections in a transaction.
          updateEmployeeInfo := func(sctx mongo.SessionContext, d db_session_service.DBTransaction) error {
              err := sctx.StartTransaction(options.Transaction().
                  SetReadConcern(readconcern.Snapshot()).
                  SetWriteConcern(writeconcern.New(writeconcern.WMajority())),
              )
              if err != nil {
                  return err
              }
              err = models.InsertNodeWithSession(sctx, node)
              if err != nil {
                  _ = sctx.AbortTransaction(sctx)
                  d.Logger.Info("caught exception during transaction, aborting.")
                  return err
              }
      
              return d.Commit(sctx)
          }
      
          return dbTransaction.Exec(models.DB.Mongo, updateEmployeeInfo)
      }
      

總結

  • 轉過來主要是官方支持事務,在切換的時候,官方用例中有很多使用的是bsonx,在mgo中使用的bson,踩了一些坑,但是你也可以保持使用bson
  • 事務還是要保證整個事務執行的所有語句都使用事務,不要混合使用(sessionContent)
  • 儘可能做一些代碼封裝吧
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章