Oracle 10g:有什么好的学术方法可以防止记录连续更新?
我们有一个名为“合同”的表。这些合同记录由外部站点上的用户创建,并且必须由内部站点上的工作人员批准或拒绝。当合同被拒绝时,它只是从数据库中删除。然而,当它被接受时,会生成一条称为“合同接受”的新记录,该记录会写入自己的表中,并从合同中存在的数据中派生出来。
问题是两名内部员工最终可能会签订同一份合同。第一个用户接受并生成合同接受记录。然后,在页面上仍打开相同的合同记录的情况下,第二个用户再次接受合同,从而创建重复的接受记录。
解决这个问题的快速而肮脏的方法是在合约被接受之前从数据库检索合约,检查状态,并生成一条错误消息,表明它已被接受。这可能适用于大多数情况,但用户仍然可以同时单击“接受”按钮并偷偷通过此验证代码。
我还考虑过在数据层深处使用线程锁,以防止两个线程同时进入同一代码区域,但该应用程序存在于两个负载平衡的服务器上,因此用户可能位于不同的服务器上,这将导致使这种方法毫无用处。
我能想到的唯一方法必须存在于数据库中。从概念上讲,我想以某种方式锁定存储过程或表,以便它不能同时更新两次,但也许我在这里对 Oracle 的了解不够。更新如何进行?更新请求是否以某种方式排队,以便它们不会在完全相同的时间发生?如果是这样,我可以检查 SQL 中记录的状态,并在输出参数中返回一个值,表明该记录已被接受。但是,如果更新请求没有排队,那么两个人仍然可以同时进入更新 sql。
寻找有关如何解决此问题的好建议。
We have a table called Contracts. These contract records are created by users on an external site and must be approved or rejected by staff on an internal site. When a contract is rejected, it's simply deleted from the db. When it's accepted, however, a new record is generated called Contract Acceptance which is written to its own table and is derived from data that exists on the contract.
The problem is that two internal staff members may each end up opening the same contract. The first user accepts and a contract acceptance record is generated. Then, with the same contract record still open on the page, the second user accepts the contract again, creating a duplicate acceptance record.
The quick and dirty way to get past this is to retrieve the contract from the db just before it's accepted, check the status, and produce an error message saying that it's already been accepted. This would probably work for most circumstances, but the users could still click the Accept button at the exact same time and sneak by this validation code.
I've also considered a thread lock deep in the data layer that prevents two threads from entering the same region of code at the same time, but the app exists on two load-balanced servers, so the users could be on separate servers which would render this approach useless.
The only method I can think of would have to exist at the database. Conceptually, I would like to somehow lock the stored procedure or table so that it can't be updated twice at the same time, but perhaps I don't understand Oracle enough here. How do updates work? Are update requests somehow queued up so that they do not occur at the exact same time? If this is so, I could check the status of the record in th SQL and return a value in an out parameter stating it has already been accepted. But if update requests aren't queued then two people could still get into the update sql at the exact same time.
Looking for good suggestions on how to go about this.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
首先,如果每个合同只能有一个合同接受,那么合同接受应该将合同 ID 作为其自己的主(或唯一)键:这将使重复成为不可能。
其次,为了防止第二个用户在第一个用户接受合同时尝试接受合同,您可以使接受过程锁定合同行:
第二个用户的接受尝试将失败并出现异常:
First, if there can only be one Contract Acceptance per Contract, then Contract Acceptance should have the Contract ID as its own primary (or unique) key: that will make duplicates impossible.
Second, to prevent the second user from trying to accept the contract while the first user is accepting it, you can make the acceptance process lock the Contract row:
The second user's attempt to accept will then fail with an exception :
一般来说,有两种解决问题的方法
选项 1:悲观锁定
在这种情况下,您是悲观的,因此在选择表中的行时将其锁定。当用户查询
Contracts
表时,他们会执行类似“无论谁先选择记录,都将锁定它”之类的操作。无论是谁第二次选择该记录,都会收到 ORA-00054 错误,应用程序随后会捕获该错误,并让他们知道另一个用户已经锁定了该记录。当第一个用户完成他们的工作时,他们将 INSERT 发出到
Contract_Acceptance
表中并提交他们的事务。这将释放Contracts
表中行的锁定。选项 2:乐观锁定
在这种情况下,您乐观地认为两个用户不会发生冲突,因此您最初不会锁定记录。相反,您可以选择所需的数据以及添加到表中的
Last_Updated_Timestamp
列(如果该列尚不存在)。当用户接受合同时,在将
INSERT
插入Contract_Acceptance
之前,他们会对合同发出UPDATE
第一个执行此更新的人将成功(该语句将更新 1 行)。第二个人执行此操作将更新 0 行。应用程序检测到更新没有修改任何行,并告诉第二个用户其他人已经处理了该行。
无论哪种情况
无论哪种情况,您可能都希望向
Contract_Acceptance
表添加UNIQUE
约束。这将确保对于任何给定的Contract_ID
,Contract_Acceptance
表中只有一行。这是第二道防线,永远不需要,但可以在应用程序未正确实现其逻辑的情况下保护您。
In general, there are two approaches to the problem
Option 1: Pessimistic Locking
In this scenario, you're pessimistic so you lock the row in the table when you select it. When a user queries the
Contracts
table, they'd do something likeWhoever selects the record first will lock it. Whoever selects the record second will get an ORA-00054 error that the application will then catch and let them know that another user has already locked the record. When the first user completes their work, they issue their INSERT into the
Contract_Acceptance
table and commit their transaction. This releases the lock on the row in theContracts
table.Option 2: Optimistic Locking
In this scenario, you're being optimistic that the two users won't conflict so you don't lock the record initially. Instead, you select the data you need along with a
Last_Updated_Timestamp
column that you add to the table if it doesn't already exist. Something likeWhen a user accepts the contract, before doing the
INSERT
intoContract_Acceptance
, they issue anUPDATE
on ContractsThe first person to do this update will succeed (the statement will update 1 row). The second person to do this will update 0 rows. The application detects the fact that the update didn't modify any rows and tells the second user that someone else has already processed the row.
In Either Case
In either case, you probably want to add a
UNIQUE
constraint to theContract_Acceptance
table. This will ensure that there is only one row in theContract_Acceptance
table for any givenContract_ID
.This is a second line of defense that should never be needed but protects you in case the application doesn't implement its logic correctly.