Database 在插入方法之前检查数据库的唯一性?

Database 在插入方法之前检查数据库的唯一性?,database,concurrency,Database,Concurrency,编辑:我想我需要代码来为我说话 关于我的问题的一个简单例子 在数据库中创建用户,电子邮件和用户名是唯一的。如果唯一性检查没有通过,我需要确切地知道哪个字段/列/是什么导致了错误。 我想要实现的内容 坏方法 void createUser(String email, String username, String password) throws EmailExistingException, UsernameExistingException { //Do some query fir

编辑:我想我需要代码来为我说话

关于我的问题的一个简单例子

在数据库中创建用户,电子邮件和用户名是唯一的。如果唯一性检查没有通过,我需要确切地知道哪个字段/列/是什么导致了错误。

我想要实现的内容

坏方法

void createUser(String email, String username, String password) 
throws EmailExistingException, UsernameExistingException
{
    //Do some query first.
    if (emailExists(email)) throw EmailExistingException;
    if (usernameExists(username)) throw UsernameExistingException;

    //TODO: Create the user.
}    
//During database creation
db.users.setUnique("email", true);
db.users.setUnique("username", true);

void createUser(String email, String username, String password) 
throws UserCreationException
{
    try {
        //TODO: Create the user.
    } catch (SomeSortOfDatabaseException e) {
        throw UserCreationException("some message", e);
    }
}
//During database creation
db.users.setUnique("email", true);
db.users.setUnique("username", true);

void createUser(String email, String username, String password) 
throws EmailExistingException, UsernameExistingException
{
    try {
        //TODO: Create the user.
    } catch (SomeSortOfDatabaseException e) {
        if (emailExists(email)) throw EmailExistingException;
        if (usernameExists(username)) throw UsernameExistingException;

        throw ServerException("some message", e);
    }
}
出了什么问题-插入前的典型检查,永远不会被接受

不错的方法

void createUser(String email, String username, String password) 
throws EmailExistingException, UsernameExistingException
{
    //Do some query first.
    if (emailExists(email)) throw EmailExistingException;
    if (usernameExists(username)) throw UsernameExistingException;

    //TODO: Create the user.
}    
//During database creation
db.users.setUnique("email", true);
db.users.setUnique("username", true);

void createUser(String email, String username, String password) 
throws UserCreationException
{
    try {
        //TODO: Create the user.
    } catch (SomeSortOfDatabaseException e) {
        throw UserCreationException("some message", e);
    }
}
//During database creation
db.users.setUnique("email", true);
db.users.setUnique("username", true);

void createUser(String email, String username, String password) 
throws EmailExistingException, UsernameExistingException
{
    try {
        //TODO: Create the user.
    } catch (SomeSortOfDatabaseException e) {
        if (emailExists(email)) throw EmailExistingException;
        if (usernameExists(username)) throw UsernameExistingException;

        throw ServerException("some message", e);
    }
}
有什么不好的-我可能只是得到一个异常,带有错误代码和字符串消息。但是我需要让用户知道复制的字段,这样他们就可以相应地修改它,这是我用这段代码做不到的。也许一些db驱动程序的错误可以提供非常具体的细节,但我认为依赖驱动程序不是一个好方法,因为如果您将来想更改数据库,它会杀死您

我的方法

void createUser(String email, String username, String password) 
throws EmailExistingException, UsernameExistingException
{
    //Do some query first.
    if (emailExists(email)) throw EmailExistingException;
    if (usernameExists(username)) throw UsernameExistingException;

    //TODO: Create the user.
}    
//During database creation
db.users.setUnique("email", true);
db.users.setUnique("username", true);

void createUser(String email, String username, String password) 
throws UserCreationException
{
    try {
        //TODO: Create the user.
    } catch (SomeSortOfDatabaseException e) {
        throw UserCreationException("some message", e);
    }
}
//During database creation
db.users.setUnique("email", true);
db.users.setUnique("username", true);

void createUser(String email, String username, String password) 
throws EmailExistingException, UsernameExistingException
{
    try {
        //TODO: Create the user.
    } catch (SomeSortOfDatabaseException e) {
        if (emailExists(email)) throw EmailExistingException;
        if (usernameExists(username)) throw UsernameExistingException;

        throw ServerException("some message", e);
    }
}

它实际上正在变得更好,但我不太喜欢我目前的方法(在现实世界中,许多独特的领域可以改变,你知道我在说什么)。所以我只是好奇,在实际生产中,解决这个问题的常用方法是什么。如果您有任何意见,我们将不胜感激。

请使用具有内置原子检查功能的insert sql:

insert into mytable (col1, col2, ...)
select * 
from (select 'foo', 'bar', ... from dual) x -- postgress, mysql, etc doesn't need "from dual"
left join mytable
    on col1 = 'foo'
   and col2 = 'bar'
   ...
where col1 is null -- only selects something when there's no match

刚刚用Java找到了一个解决方案

//During database creation, make restrictions.

void createUser(String email, String username, String password) 
throws EmailExistingException, UsernameExistingException
{
    String email_intern = email.intern();
    String username_intern = username.intern();
    synchronized(email_intern) {
        synchronized(username_intern) {
            //Do some query first.
            if (emailExists(email)) throw EmailExistingException;
            if (usernameExists(username)) throw UsernameExistingException;

            //TODO: Create the user.
        }
    }
}
看起来很有希望。intern()确保相同的字符串值在同一实例上同步。这种方法对吞吐量的影响非常小,同时它消除了该方法中所有可能的并发问题

值得注意的事情

  • 在传递到此处之前,请确保用户名不在电子邮件模式中
  • 它只有一个可能的锁顺序,不必担心死锁问题
  • 如果这种锁也用于其他方法,那么必须注意锁的顺序
  • Tripple检查用于服务器的JVM,确保在GC期间收集插入的字符串

哪个数据库?有些数据库以本机方式支持这一点。“有些数据库以本机方式支持这一点。”这肯定是我在该不太糟糕的方法中提到的问题。无论如何,我很想知道哪些数据库支持它,以及它是如何实现的,当然是线程安全的。mysql已经
更新了。。。例如,关于重复键…
。感谢您的回复,但实际上我正在寻找一种更通用的方法。不管它是sql还是nosql数据库。@toyknight没有更多的“通用方法”,需要专门化。即使是特定(SQL)数据库实现中给定实现的“安全性”也存在问题:这种分歧问题只有在扩展到“任意”数据库类型时才会加剧。@toyknight它没有比这更一般的了。这适用于所有数据库,并且是原子的——在多线程环境中无需担心竞争条件(由关系数据库契约保证)。@Bohemian我知道它是原子的。我想知道的是,如果插入不成功,将复制哪个列。据我所知,mysql驱动程序只在更新后返回,有多少行受到影响?您还没有摆脱所有可能的并发问题。我假设您的
emailExists
usernameExists
方法都在对表进行查询。在调用这两个方法之间,另一个线程/进程/应用程序可能会插入/修改/删除电子邮件/密码行。“这种方法可以做到,因为它几乎涵盖所有情况,但您还必须实现Bohemian在回答中所示的原子检查。”BasilBourque表示同意。但是由于锁的原因,在存在性检查期间插入相同的电子邮件/用户名是不可能的(假设createUser是唯一发生插入的地方)。对于修改,我们可以对所有相关方法执行类似的锁定(必须处理锁定顺序)。无论如何,设置数据库限制始终是一种好的做法。