【问题标题】:How to ensure uniqueness of a property in a NoSQL record ( Golang + tiedot )如何确保 NoSQL 记录中属性的唯一性(Golang +tiedot)
【发布时间】:2017-02-25 03:55:18
【问题描述】:

我正在开发一个用 golang 编写的简单应用程序,使用 tiedot 作为 NoSQL 数据库引擎。 我需要在数据库中存储一些用户。

type User struct {
    Login        string
    PasswordHash string
    Salt         string
}

当然,两个用户不能有相同的登录名,而且 - 由于这个引擎不提供任何事务机制 - 我想知道在编写时如何确保数据库中没有重复登录。

我首先认为我可以在插入之前通过登录搜索用户,但因为数据库将是 同时使用,不可靠。

也许我可以等待一个随机时间,如果集合中有另一个用户使用相同的登录名,请将其删除,但这听起来也不可靠。

这是否可能,或者我应该切换到支持事务的数据库引擎?

【问题讨论】:

    标签: go unique tiedot nosql


    【解决方案1】:

    我一开始以为可以在插入前通过登录搜索用户,但是由于数据库会被并发使用,所以不可靠。

    是的,它创建了一个竞争条件。解决此问题的唯一方法是:

    1. 锁定表格
    2. 搜索登录名
    3. 如果未找到登录名则插入
    4. 解锁表

    表锁不是一个可扩展的解决方案,因为它会在您的应用程序中造成代价高昂的瓶颈。这就是 MySQL 的 MyISAM 等非事务性存储引擎被淘汰的原因。这就是 MongoDB 必须使用集群来扩展的原因。

    如果您的数据集大小较小且并发量较少,则它可以工作,因此它可能足以在使用较少的网站上创建登录。新登录可能不会创建得如此频繁,以至于他们需要扩大规模。

    但用户登录、密码更改或帐户属性的其他更改确实发生得更频繁。

    解决方案是使这个操作原子化,以避免竞争条件。例如,尝试插入并让数据库引擎验证唯一性,如果违反该约束,则拒绝插入。

    不幸的是,我在tiedot 中没有看到任何文档表明它支持唯一约束或对索引执行唯一性。

    Tiedot 98% 是由单个开发人员在大约 2 年的时间(2013 年 5 月 - 2015 年 4 月)内编写的。从那时起很少有活动(见https://www.openhub.net/p/tiedot)。我认为tiedot 是一个实验性项目,不太可能扩展功能集。

    【讨论】:

    • 是的,你是对的。我已经切换到 à SQLite 后端,因为我不想浪费更多时间尝试另一种解决方案。我对 github 上的 1000+ 颗星感到太自信了,我的错……感谢您花时间回答。
    【解决方案2】:

    以下是我的解决方案。它不是 Tiedot 特有的,但它使用CQRS 并且可以应用于各种数据库。

    您还可以使用它获得其他好处,例如缓存和批量写入(如果 DB 支持它),以防止在每次请求时询问 DB。

    package main
    
    import (
        "sync"
        "log"
        "errors"
    )
    
    type User struct {
        Login        string
        PasswordHash string
        Salt         string
    }
    
    type MutexedUser struct {
        sync.RWMutex
        Map map[string]User
    }
    
    var u = &MutexedUser{}
    
    func main() {
        var user User
    
        u.Sync()
        // Get new user here
        //...
        if err := u.Insert(user); err != nil {
            // Ask to provide new login
            //...
            log.Println(err)
        }
    }
    
    func (u *MutexedUser) Insert(user User) (err error) {
        u.Lock()
        if _, ok := u.Map[user.Login]; !ok {
            u.Map[user.Login] = user
            // Add user to DB
            //...
            u.Unlock()
            return err
        }
        u.Unlock()
        return errors.New("duplicated login")
    }
    
    func (u *MutexedUser) Read(login string) User {
        u.RLock()
        value := u.Map[login]
        u.RUnlock()
    
        return value
    }
    
    func (u *MutexedUser) Sync() (err error) {
        var users []User
    
        u.Lock()
        defer u.Unlock()
        // Read users from DB
        //...
        u.Map = make(map[string]User)
        for _, user := range users {
            u.Map[user.Login] = user
        }
        return err
    }
    

    【讨论】:

    • 所以你真的将整个数据库表作为映射读入内存?当您的表太大而无法放入应用程序内存时会发生什么?
    • 此外,如果这是一个 Web 应用程序,您可以让此代码同时运行数百或数千个请求。但是,如果您对地图的访问进行互斥,您只是导致您的网站连续运行请求。
    • 不一定要保留所有数据库,只能是登录名。此外,我正在使用 u.RLock() 来最大程度地减少等待阅读请求的时间。是的,我确实同时有数千个请求。
    • 对,强制登录名的唯一性是一回事。该表可能很小,并且创建的新登录可以容忍一些序列化。但这不是任何表的任何列中唯一性的通用解决方案。
    • 当然,它仅在当前任务的上下文中。当每个服务都需要自己的数据库时,我主要将这种方法与微服务架构一起使用。
    猜你喜欢
    • 2023-04-04
    • 2016-04-21
    • 2021-12-30
    • 1970-01-01
    • 2018-05-09
    • 2016-01-10
    • 1970-01-01
    • 2016-01-02
    • 1970-01-01
    相关资源
    最近更新 更多