3

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

  1. 证实
  2. 列出错误或插入/更新数据

在这个特定的场景中,我正在开发一个用户注册过程,但我试图为所有类型的表单找到一个通用的解决方案,基于检查数据库表中唯一值的可用性。

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

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

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

我要解决的问题是确保唯一值在检查其可用性之后并将其插入一个HTTP 请求之前可用。另一个用户注册了相同的唯一登录名,而第一个用户弄乱了他的密码并且密码确认不一样,这没关系。

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


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

表锁定

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

  1. 锁定表以进行写入
  2. 检查可用性
  3. 返回错误或插入行
  4. 解锁桌子

有人说锁定整个表是最糟糕的解决方案。也许是这样,但这是我自己想出的唯一可行的方法。

锁只在一个 HTTP 请求期间保留,当然不会在几个请求之间保留。

插入并捕获错误

这种方式是其他一些人向我建议的。他们建议将该列设为唯一索引列,并在两个阶段分别验证和检查唯一性。过程是这样的:

  1. 验证数据
  2. 如果验证成功,则插入该行
  3. 如果插入行失败显示唯一值不可用的错误

当然,我已将该列设为唯一索引列。但这并不意味着我想使用数据库的能力在验证时抛出错误;它应该在应用程序级别完成。

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

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

4

3 回答 3

1

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

Try/catch 是我执行唯一键绑定插入的方式。在我看来,这是最好的方法。您必须意识到,任何使用行级锁定的事务数据库在任何时候都容易受到死锁 的影响。即使在正常的、无趣的普通查询中也是如此。考虑到这一点,任何使用事务数据库的应用程序在技术上都应该在 try/catch 块中执行每个执行的写查询!

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

最重要的是,您可以避免的锁越多,您的应用程序的可扩展性就越高。即使您可以SELECT...FOR UPDATE对不存在的值使用,这样做也会显着增加该表上的死锁数量。由于使用 try/catch 很容易避免这种情况,因此我一直使用 try/catch。同样,很容易为您的数据库驱动程序创建一个通用错误处理程序包装器,以找出常见错误,例如唯一键或死锁,并适当地处理它们。

于 2010-01-27T06:19:23.333 回答
0

正如您所说,“插入并捕获错误”很容易编码并且不会锁定用户,如果您跳过第 1 步而只是尝试插入或失败,那么您可以节省往返行程,这对于大量加载的服务器。

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

于 2010-01-27T06:13:47.567 回答
0

新建表AvailableUsers,列ID、ClaimedUserName、TimeStamp、SessionID或以对象的形式存储在session中。

验证后,如果新用户第一次使用已声明的用户名发帖,您可以从会话或数据库中检查。

于 2010-01-27T06:18:22.257 回答