我有一个网络应用程序。在其中处理表单的流程如下:
在这个特定的场景中,我正在开发一个用户注册过程,但我试图为所有类型的表单找到一个通用的解决方案,基于检查数据库表中唯一值的可用性。
在此用户注册中,用户的登录名必须是唯一的。在验证阶段,应用程序检查其在数据库表中的可用性,如果可用则插入一行。还有其他字段也必须验证,例如密码和密码确认。所有验证在一个 HTTP 请求中发生一次。
问题是我不能确定 之后 应用程序检查了它的可用性,它没有被并行进程中的其他用户使用之前 第一个用户的进程插入它。我知道两个用户在同一毫秒内输入相同登录名的可能性非常小,但有一天可能会出现这种情况,即数千名用户同时向某个表单输入数据。
如果验证已经通过,用户不应该看到一些错误消息说他的登录已经注册。
我要解决的是确保唯一值在检查其可用性之后并将其插入 之前是可用的。一个 HTTP 请求。另一个用户注册了相同的唯一登录名,而第一个用户弄乱了他的密码并且密码确认不一样,这没关系。
使用现有行可以轻松解决此问题,因为我可以 SELECT 它 FOR UPDATE 并且它将在事务期间被锁定。但我不能对 做同样的事情不存在排。那就是问题所在。我该如何解决这个问题?
以下是我知道的一些解决方案。我不确定其中哪一个是最好的。另外,我不确定我知道最好的方法,所以请分享你知道的方法。
表锁
过去我已经用表锁定解决了这个问题,但我不确定这是不是最好的方法。过程是这样的:
有人说锁定整个表是最糟糕的解决方案。也许是这样,但这是我自己想出的唯一可行的方法。
锁只在一个 HTTP 请求期间保留,当然不会在几个请求之间保留。
插入并捕获错误
这种方式是其他一些人向我建议的。他们建议将该列设为唯一索引列,并在两个阶段分别验证和检查唯一性。过程是这样的:
当然,我已将该列设为唯一索引列。但这并不意味着我想使用数据库的能力在验证时抛出错误;它应该在应用程序级别完成。
我不喜欢这种方式,因为我不喜欢这种情况下的 try-and-catch-an-exception 方式,因为在检查值的可用性并插入它的过程中没有任何异常。我认为它应该采用检查和保留和插入的方式。我认为验证用户输入不应该基于异常,因为用户输入错误并没有什么异常。
我可能错了,但这是我目前的观点。如果你认为我明显错了,请告诉我为什么。
最佳答案
第一件事是:表锁定远非理想的解决方案。如果您预计将其扩展到数千个并发用户,锁定整个表是使您的数据库停止运行的可靠方法。您需要尽可能远离表锁定,以便拥有适当可扩展的应用程序。
Try/catch 是我执行唯一键绑定(bind)插入的方式。在我看来,这是最好的方法。您必须意识到,任何使用行级锁定的事务数据库都是 vulnerable to deadlocks 随时 .即使在正常的、无趣的普通查询中也是如此。考虑到这一点,任何使用事务数据库的应用程序在技术上都应该具有 在 try/catch block 中执行的每个写入查询!
当然,没有多少人会这样发展,因为在正常的日常使用中,这种情况不会经常发生。但是数据库“错误”并不总是真正意义上的错误,即您做错了什么。它们是传达数据状态的正常方式。
最重要的是,您可以避免的锁越多,您的应用程序的可扩展性就越高。即使你可以使用 SELECT...FOR UPDATE
在不存在的值上,这样做可能会显着增加该表上的死锁数量。由于使用 try/catch 很容易避免这种情况,因此我一直使用 try/catch。同样,很容易为您的数据库驱动程序创建一个通用错误处理程序包装器,以找出常见错误,例如唯一键或死锁,并适本地处理它们。
关于validation - 一种检查行不存在并将其插入原子的常用方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2144854/