检查行不存在并将其插入原子的常用方法?

时间:2010-01-27 05:58:13

标签: validation transactions unique atomic

我有一个网络应用程序。处理表单的流程如下:

  1. 验证
  2. 列出错误或插入/更新数据
  3. 在这个特定场景中,我正在开发一个用户注册流程,但我正在尝试为所有类型的表单找到一个通用的解决方案,它基于检查数据库表中唯一值的可用性。

    在此用户注册中,用户的登录名必须是唯一的。在验证阶段,应用程序在数据库表中检查其可用性,如果可用,则插入一行。还有其他必须验证的字段,如密码和密码确认。所有验证都在一个HTTP请求中发生一次。

    问题是我无法确定之后检查应用程序的可用性,并非其他用户在并行进程之前第一个用户插入它。我知道两个用户在相同的毫秒内输入相同的登录的可能性非常小,但有一天可能会出现另一种情况,即数千名用户同时将数据输入某种形式。

    如果验证已经通过,用户不应该看到一些错误消息,说明他的登录已经注册。

    我要解决的是确保在检查其可用性之后以及在一个 HTTP请求中插入之前,该唯一值是否可用。没有其他用户注册相同的唯一登录,而第一个用户的密码和密码确认不一致,这是可以的。

    使用现有行可以轻松解决此问题,因为我可以选择它进行更新并在事务期间将其锁定。但我不能对不存在行做同样的事情。那就是问题所在。我该如何解决这个问题?


    以下是我所知道的一些解决方案。我不确定哪一个是最好的。更多,我不确定我知道最好的方法,所以请分享你所知道的方式。

    表格锁定

    我过去已经用表锁定解决了这个问题,但我不确定这是最好的方法。这个过程是这样的:

    1. 锁定表格以进行写入
    2. 检查可用性
    3. 返回错误或插入行
    4. 解锁表格
    5. 有人说锁定整个桌子是最糟糕的解决方案。也许它是,但这是我自己想出来的唯一方法。

      锁定仅在一个HTTP请求期间保留,当然不在其中几个请求之间。

      插入并发现错误

      其他人向我建议这种方式。他们建议将该列作为一个独特的索引列,并在两个阶段中单独验证和检查唯一性。过程如下:

      1. 验证数据
      2. 如果验证成功,请插入行
      3. 如果插入行失败,则显示唯一值不可用的错误
      4. 当然,我已将该列设为唯一索引列。但这并不意味着我想利用数据库的能力在验证时抛出错误;它应该在应用程序级别完成。

        我不喜欢这种方式,因为我不喜欢这种情况下的try-and-catch-an-exception方法,因为在检查值的可用性和插入它的过程中没有什么特别之处。我认为它应该采用检查和保留和插入的方式。我认为验证用户输入不应该基于异常,因为用户输入错误没有例外。

        我可能错了,但这是我目前的观点。如果你认为我明显错了,请告诉我原因。

3 个答案:

答案 0 :(得分:1)

首先是第一件事:表锁定远非理想的解决方案。如果您预计将此扩展到数千个并发用户,则锁定整个表是一种可靠的方法,可以使您的数据库停止运行。您需要尽可能远离表锁定才能拥有适当的可伸缩应用程序。

尝试/ catch是我执行唯一键绑定插入的方式。在我看来,这是最好的方法。您必须意识到的是,任何利用行级锁定的事务数据库都可以随时vulnerable to deadlocks 。即使是正常的,没有趣味的东西,普通的查询。考虑到这一点,任何使用事务数据库的应用程序在技术上都应该在try / catch块中具有 每个执行的写入查询!

当然没有多少人像这样开发,因为在正常的日常使用中,这种情况经常不会发生。但是数据库“错误”并不总是真正意义上的错误你做错了。它们是传达数据状态的常用方式。

最重要的是,您可以避免的锁越多,您的应用程序的可扩展性就越高。即使您可以对不存在的值使用SELECT...FOR UPDATE,这样做也会显着增加该表上的死锁数。因为使用try / catch很容易避免这种情况,所以我总是使用try / catch。同样,为数据库驱动程序添加通用错误处理程序包装器来挑选常见错误(例如唯一键或死锁)并正确处理它们非常容易。

答案 1 :(得分:0)

正如您所说,“插入并捕获错误”很容易编码并且不会锁定用户,如果您跳过步骤1并尝试插入或失败,那么您可以保存往返可能很重要对于负载很重的服务器。

另一个选项是在内存中保留一个临时保留ID的列表,如果一段时间后没有永久声明,则会释放该列表。由于您必须了解其他用户,因此您需要使用线程安全的集合,并可能在用户会话中将其键入。

答案 2 :(得分:0)

创建一个新表 AvailableUsers ,其列为ID,ClaimedUserName,TimeStamp,SessionID 或以对象的形式存储在会话中。

验证后,如果新用户首先发布了已声明的用户名,则可以进行检查 来自您的会话或数据库。