我们正在构建一个 ajax 应用程序。我们希望多个用户可以同时访问该应用程序。因此,我想以某种方式运行每个更新查询,以便它们提供字段的先前值。关键是如果之前的值不是系统预期的值,PostgreSQL 应该抛出一个错误(这意味着在我们的用户看到该值之后其他人修改了它)。由于不存在并发问题,因此在一个事务和数据库级别上处理这个问题会很巧妙。这可能吗?
3 回答
因此,我想以某种方式运行每个更新查询,以便它们提供字段的先前值
您正在描述一种称为“乐观并发控制”或“乐观锁定”的技巧。这是公认的和经过验证的。
看:
- http://en.wikipedia.org/wiki/Optimistic_concurrency_control
- http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/transactions.html#transactions-optimistic(对于 Hibernate,一种 Java ORM,但原理适用于许多系统)
甚至 ODBC 早在当年就这样做了,尽管 ODBC 使用的原始方法有一些……问题。
通常最好有一个专门的修订列,它应该是一个随着每次更新而递增的整数。不要使用时间戳。
您可以执行以下操作:
SELECT col1, version FROM sometable WHERE id = 42;
想象一下,您将获得('bob',7)
然后运行的结果行:
UPDATE sometable SET col1 = 'fred' WHERE id = 42 AND version = 7;
并检查有多少行受到影响。如果它为零,您就知道发生了更新冲突。
如果您在同一个数据库中混合使用乐观锁定和传统应用程序,您可以使用触发器来确保乐观锁定列始终递增,以便您的传统应用程序和操作锁定应用程序可以很好地协同工作。
顺便说一下,这种策略不适用于 PostgreSQL 中的触发器分区表。
如果您想在数据库中执行此操作,我认为您将不得不在表上使用触发器。在触发器中,您可以检查“旧”(现在表中的内容)与“新”(您发送的更新内容)。我认为您不能为此使用规则。您还可以使用表中最后修改的列,而不是传递和比较所有列。
CREATE TRIGGER check_update
BEFORE UPDATE ON table
FOR EACH ROW
WHEN (OLD.timestamp IS DISTINCT FROM NEW.timestamp)
EXECUTE PROCEDURE check_update();
如果您不想使用时间戳,可以在 WHEN 子句中检查所有列。然后,您可以让 check_update() 过程执行某些操作,可能引发异常或返回当前存在的数据。可能有更好的方法,但这应该有效。
postgreSQL MVCC 模型可以确保在您的用户事务读取和保存数据更改时没有人更改数据。这将特别通过使用可序列化事务级别来强制执行……但是。
你真正的问题不是保存阶段,在这个阶段很容易打开一个事务、读取数据、改变它并提交这个事务;您的问题是网络问题。您无法打开事务、读取数据、通过您的 Web 应用程序将其推送给最终用户,然后等待几秒钟/几分钟的 ajax 请求,然后保存更改的数据并提交。交易不会长时间保持打开状态,并在用户发布后恢复。
因此,在 Web 应用程序服务器端,您拥有的是一个表单帖子,并且您想要保存更改后的数据。您特别想检测在您的用户加载页面、去吃东西和决定按下提交按钮之间发生变化的数据。一种方法是在您的用户帖子中存储原始未更改数据的副本,并将此数据与当前存储数据进行比较。另一种方法是用@Scott S 的解决方案之类的东西标记数据库中的数据记录,最后修改时间戳。并在每次修改后重新计算(可能使用触发器)。然后,您将在用户表单上嵌入此(或这些)数据签名,并在打开保存事务后,您要做的第一件事是比较 md5sum 并在数据似乎已更改时回滚整个操作。
这不是数据库角色,它是一个应用程序逻辑,因为数据库只会管理异步数据操作(事务隔离管理和锁),而不是长期的用户共享数据策略。您的应用程序必须通过异步表单 lobd 和帖子处理共享策略。例如,参见维基百科处理它的方式。如果您在编辑模式下从多个位置加载同一页面并尝试保存两者,则会检测到先前的修改并拒绝您的第二次保存。老实说,绝大多数 Web 应用程序都忽略了这个问题,最后一次提交获胜。
更新
为了进一步使用 ajax 管理同步修改,您可以构建一个相当复杂的系统,使用LISTEN和NOTIFY SQL 关键字来检测修改并在客户端推回这些东西。如果您的应用程序服务器是持久性的(不像 PHP 或 jsp),并且如果您将持久性 ajax 连接模拟与Comet或其他推送技术等系统一起使用,然后您可以在另一个用户进行事务提交更改其表单中显示的某些数据时通知 Web 表单上的用户。这不会消除用户在非常近的时间提交修改的问题(并行用户请求处理服务器端)。对于最后一种情况,您唯一的解决方案是使用一个好的事务,也许在可序列化的杠杆中,并始终管理用户修改可能被服务器端拒绝的事实(这是事务的关键,始终管理事务可以滚动的事实即使您没有犯错,服务器也会返回,只是因为修改与并行提交的事务冲突)。