mgo 和 mongo-go-driver 比较
库介绍
-
mgo:是 MongoDB 的 Go 语言驱动,它用基于 Go 语法的简单 API 实现了丰富的特性,并经过良好测试。使用起来很顺手,文档足够,前期一直在使用,可惜是不维护了;
- 官网,
- 文档
- github
-
mongo-go-driver:官方的驱动,设计的很底层,从 mgo 转来的时候不是很顺手,主要是使用事务;
- github
用法介绍
连接数据库
-
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)
- 尽可能做一些代码封装吧