Comparison of mgo and mongo-go-driver
Introduction to Library
-
mgo: It's driven by MongoDB's Go language. It uses a simple API based on Go grammar to implement rich features and has been well tested. It's easy to use, the documents are enough, and it has been used in the early stage, but it's a pity that it's not maintained.
-
mongo-go-driver: Official driver, very low-level design, transfer from mgo is not easy, mainly using transactions;
Usage introduction
Connect to the database
-
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"))
The connection between the two databases is very simple. The latter uses options, which can set the number of connections, connection time, socket time, timeout, etc.
Indexes
-
mgo
func createUniqueIndex(collection string, keys ...string) { ms, c := Connect(setting.DatabaseSetting.DBName, collection) defer ms.Close() // Setting unique index for statistical tables index := mgo.Index{ Key: keys, // Index key Unique: true, // unique index DropDups: true, // When created after data exists, duplicate data is automatically deleted Background: true, // Write locks are not occupied for a long time } // Create index 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{} // Composite index 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)) } } // Create index 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 can construct composite index directly, and create index (addrTrxC,'address_id','asset_id') by passing in multiple parameters in sequence, but mongo-go-driver needs to do some processing by itself.
query
-
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 }
When querying a single element, both drivers are convenient. However, when querying multiple elements, mongo-go-driver can not directly parse into an array. It needs to use cursor type to traverse and parse all data (troublesome, need to encapsulate itself).
insert
-
mgo
// currency func Insert(db, collection string, docs ...interface{}) error { ms, c := Connect(db, collection) defer ms.Close() return c.Insert(docs...) } //insert 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 }
The difference in insertion is not very big either.
modify
-
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) }
Both can update one mgo(update) mongo-go-driver(updateOne) and multiple mgo(updateAll) mongo-go-driver(updateMany); but when upsert, mgo can use upsert directly, and mongo-go-driver needs to set opts: = options. Update (). Setsert (true).
delete
-
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 }
Delete is also relatively simple, roughly the same.
affair
-
mgo
I won't support it -
mongo-go-driver
- Need to use SessionContext
-
Use SessionContext for all changes you need to query before making them.
Because it is used in many places, it encapsulates a method. //The method to be implemented in this method is Exec's 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) }, ) } //Specific call 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) }
summary
- In turn, it's mainly official support transactions. When switching, many of the official use cases use bsonx. The bson used in mgo stepped on some pits, but you can also keep using bson.
- Transactions also need to ensure that all statements executed by the whole transaction use transactions, not session Content.
- Do as much encapsulation as possible