在事务数据库中撤消
我不知道如何使用事务数据库实现用户友好界面的撤消属性。
一方面,建议用户具有多级(无限)撤消可能性,如所述此处在答案中。 可能有助于解决此问题的模式是 Memento 或 命令。
然而,使用包括触发器、不断增长的序列号和不可逆过程的复杂数据库,很难想象撤消操作如何在事务边界之外的不同点上工作。 换句话说,撤消到最后一次提交的事务的某个点只是回滚,但是怎么可能回到不同的时刻呢?
更新(基于迄今为止的答案):我不一定希望在修改已提交时撤消起作用,我将重点关注具有开放事务的正在运行的应用程序。 每当用户单击“保存”时,就意味着提交,但在保存之前(在同一事务期间)应该可以进行撤消。 我知道使用数据库作为持久层只是一个实现细节,用户不应该打扰它。 但是,如果我们认为“数据库中的撤消和 GUI 中的撤消概念是根本不同的”并且我们不对数据库使用撤消,那么无限撤消只是一个流行词。 我知道“回滚......不是用户撤消”。
那么,考虑到同一事务内“任何更改所导致的级联效应”,如何实现客户端级别的撤消呢?
I do not know how to implement undo property of user-friendly interfaces using a transactional database.
On one hand it is advisable that the user has multilevel (infinite) undoing possibility as it is stated here in the answer. Patterns that may help in this problem are Memento or Command.
However, using a complex database including triggers, ever-growing sequence numbers, and uninvertable procedures it is hard to imagine how an undo action may work at different points than transaction boundaries.
In other words, undo to a point when the transaction committed for the last time is simply a rollback, but how is it possible to go back to different moments?
UPDATE (based on the answers so far): I do not necessarily want that the undo works when the modification is already committed, I would focus on a running application with an open transaction. Whenever the user clicks on save it means a commit, but before save - during the same transaction - the undo should work. I know that using database as a persistent layer is just an implementation detail and the user should not bother with it. But if we think that "The idea of undo in a database and in a GUI are fundamentally different things" and we do not use undo with a database then the infinite undoing is just a buzzword.
I know that "rollback is ... not a user-undo".
So how to implement a client-level undo given the "cascading effects as the result of any change" inside the same transaction?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
数据库中的撤消和 GUI 中的撤消概念是根本不同的。 GUI 将成为单用户应用程序,与其他组件的交互程度较低; 数据库是一个多用户应用程序,任何更改都可能会产生级联效应。
要做的事情是允许用户尝试将之前的状态应用为新事务,这可能有效,也可能无效; 或者在提交后不进行撤消(类似于保存后不进行撤消,这是许多应用程序中的一个选项)。
The idea of undo in a database and in a GUI are fundamentally different things; the GUI is going to be a single user application, with low levels of interaction with other components; a database is a multi-user application where changes can have cascading effects as the result of any change.
The thing to do is allow the user to try and apply the previous state as a new transaction, which may or may not work; or alternatively just don't have undos after a commit (similar to no undos after a save, which is an option in many applications).
一些(全部?)DBMS 支持保存点,允许部分回滚:
在上面的示例中,只有第一个插入会保留,其他两个将被撤消。
由于您给出的原因*以及可能的其他原因,我认为一般来说在提交后尝试撤消是不实际的。 如果在某些情况下这是必要的,那么您将必须构建大量代码来完成它,并考虑触发器等的影响。
Some (all?) DBMSs support savepoints, which allow partial rollbacks:
In the above example, only the first insert would remain, the other two would have been undone.
I don't think it is practical in general to attempt undo after commit, for the reasons you gave* and probably others. If it is essential in some scenario then you will have to build a lot of code to do it, and take into account the effects of triggers etc.
我们通过跟踪应用于数据的所有事务(不是全部,只是 3 个月以内的事务),在数据库中开发了这种可能性。 基本想法是能够看到谁在何时做了什么。 每条数据库记录均由其 GUID 唯一标识,可被视为一个 INSERT、多个 UPDATE 语句以及最后一个 DELETE 语句的结果。 由于我们跟踪所有这些 SQL 语句,并且由于 INSERT 是全局插入(所有字段值的跟踪都保留在 INSERT 语句中),因此可以:
使用以下规则“撤消”所有先前的交易:
“插入”撤消是基于唯一标识符的“删除”
“UPDATE”撤消相当于之前的“UPDATE”
“DELETE”撤消相当于第一个 INSERT 后跟所有更新
因为我们不跟踪
超过 3 个月的交易,
UNDO 并不总是可用。
对这些功能的访问严格限于数据库管理员,因为其他用户不允许在业务规则之外进行任何数据更新(例如:一旦采购订单发生,采购订单行上的“撤消”的含义是什么?已得到供应商同意吗?)。 说实话,我们很少使用此选项(一年几次?)
We developped such a possibility in our database by keeping track of all transactions applied to the data (not really all, just the ones that are less than 3 months old). The basic idea was to be able to see who did what and when. Each database record, uniquely identified by its GUID, can then be considered as the result of one INSERT, multiple UPDATEs statements, and finally one DELETE statement. As we keep tracks of all these SQL statements, and as INSERTs are global INSERTS (a track of all fields value is kept in the INSERT statement), it is then possible to:
'undo' all previous transactions with the following rules:
an 'INSERT' undo is a 'DELETE' based on the unique identifier
an 'UPDATE' undo is equivalent to the previous 'UPDATE'
a 'DELETE' undo is equilavent to the first INSERT followed by all updates
As we do not keep tracks of
transactions older than 3 months,
UNDO's are not allways available.
Access to these functionalities are strictly limited to database managers only, as other users are not allowed to do any data update outside of the business rules (example: what would be the meaning of an 'undo' on a Purchase Order line once the Purchase Order has been agreed by the supplier?). To tell you the truth, we use this option very rarely (a few times a year?)
这与 William 的帖子(我实际上投票赞成)几乎相同,但我尝试指出更详细的内容,为什么有必要实现用户撤消(相对于使用数据库回滚)。
了解更多有关您的应用程序的信息会很有帮助,但我认为对于用户(友好的)撤消/重做,数据库不是实现该功能的足够层。
从我的角度来看,数据库是实现细节,一个工具,您用作存储数据的程序员。 回滚是一种可以帮助您执行此操作的撤消,而不是用户撤消。 使用回滚意味着让用户参与他不想知道和不理解(并且不必)的事情,这从来都不是一个好主意。
正如 William 所发布的,您需要在客户端内部或服务器端实现作为会话的一部分,它跟踪您定义为用户事务的步骤并能够撤消这些步骤。 如果数据库事务是在这些用户事务期间进行的,则需要其他数据库事务来撤销这些事务(如果可能)。 如果无法撤消,请确保提供有价值的反馈,这又意味着,从业务角度而不是从数据库角度进行解释。
It's nearly the same like William's post (which I actually voted up), but I try to point out a little more detailed, why it is neccessary to implement a user undo (vs. using a database rollback).
It would be helpful to know more about your application, but I think for a user(-friendly) Undo/Redo the database is not the adequate layer to implement the feature.
The database from my point of view is implementation detail, a tool, you use as a programmer for storing data. The rollback is a kind of undo that helps YOU in doing so, it's not a user-undo. Using the rollback would mean to involve the user in things he doesn't want to know about and does not understand (and doesn't have to), which is never a good idea.
As William posted, you need an implementation inside the client or on server side as part of a session, which tracks the steps of what you define as user-transaction and is able to undo these. If database transactions were made during those user transactions you need other db transactions to revoke these (if possible). Make sure to give valuable feedback if the undo is not possible, which again means, explain it business-wise not database-wise.
为了维护任意回滚到先前的语义,您需要在数据库中实现逻辑删除。 其工作原理如下:
每个记录都有一个“已删除”标志、版本号和/或“当前指示器”标志,具体取决于您需要重建的聪明程度。 此外,您需要跨该实体的所有版本的每个实体键,以便您知道哪些记录实际引用哪个特定实体。 如果您需要知道版本适用的时间,您还可以使用“从”和“到”列。
当您删除一条记录时,您将其标记为“已删除”。 当您更改它时,您会创建一个新行并更新旧行以反映其过时情况。 有了版本号,你就可以找到之前的版本号来回滚。
如果您需要参照完整性(您可能需要,即使您认为不需要)并且可以处理额外的 I/O,您还应该有一个父表,其中的键作为所有版本记录的占位符。
在 Oracle 上,聚簇表对此很有用; 父表和版本表可以位于同一位置,从而最大限度地减少 I/O 开销。 在 SQL Server 上,包含该键的覆盖索引(可能聚集在实体键上)将减少额外的 I/O。
To maintain arbitrary rollback-to-previous semantics you will need to implement logical deletion in your database. This works as follows:
Each record has a 'Deleted' flag, version number and/or 'Current Indicator' flag, depending on how clever you need your reconstruction to be. In addition, you need a per entity key across all versions of that entity so you know which records actually refer to which specific entity. If you need to know the times a version was applicable to, you can also have 'From' and 'To' columns.
When you delete a record, you mark it as 'deleted'. When you change it, you create a new row and update the old row to reflect its obsolescence. With the version number, you can just find the previous number to roll back to.
If you need referential integrity (which you probably do, even if you think you don't) and can cope with the extra I/O you should also have a parent table with the key as a placeholder for the record across all versions.
On Oracle, clustered tables are useful for this; both the parent and version tables can be co-located, minimising the overhead for the I/O. On SQL Server, a covering index (possibly clustered on the entity key) including the key will reduce the extra I/O.