|
Re: How to use Optimistic locking in my Package [message #673213 is a reply to message #673184] |
Mon, 12 November 2018 03:33 |
cookiemonster
Messages: 13920 Registered: September 2008 Location: Rainy Manchester
|
Senior Member |
|
|
Michel Cadot wrote on Sat, 10 November 2018 19:29
Quote:I implemented an optimistic lock with the trigger
Doesn't work and is plain stupid.
Whether that works or not depends on the code the client runs.
If the client selects tcn and then updates tcn to the value they've got + 1 I don't see any reason why it wouldn't work.
|
|
|
|
Re: How to use Optimistic locking in my Package [message #673218 is a reply to message #673216] |
Mon, 12 November 2018 06:01 |
cookiemonster
Messages: 13920 Registered: September 2008 Location: Rainy Manchester
|
Senior Member |
|
|
?
Optimistic locking isn't a SQL Server specific thing.
And implementing it always has relied on the devs on controlling all the update statements and making sure each one implements it. Devs controlling all DML on an app is the norm.
|
|
|
|
Re: How to use Optimistic locking in my Package [message #673220 is a reply to message #673219] |
Mon, 12 November 2018 06:27 |
|
petar97
Messages: 120 Registered: October 2018
|
Senior Member |
|
|
Trigger work well, but here they told me it was stupid
I solved my problem within procedure, code look like this:
PROCEDURE upd_account
( p_acc_id accounts.acc_id%type
, p_acc_name accounts.acc_name%type
, p_acc_amount accounts.acc_amount%type
, p_acc_date accounts.acc_date%type
, p_version accounts.version%type
)
IS
BEGIN
UPDATE accounts
set acc_name = p_acc_name
, acc_amount = p_acc_amount
, acc_date = p_acc_date
, version = p_version + 1
where version = p_version;
DBMS_OUTPUT.PUT_LINE ('Number of updated records: ' || TO_CHAR(SQL%ROWCOUNT));
if(SQL%ROWCOUNT = 0)
THEN
RAISE_APPLICATION_ERROR( -20001, 'Oops, the row has changed since you read it.' );
END IF;
END;
|
|
|
|
|
Re: How to use Optimistic locking in my Package [message #673223 is a reply to message #673219] |
Mon, 12 November 2018 06:51 |
cookiemonster
Messages: 13920 Registered: September 2008 Location: Rainy Manchester
|
Senior Member |
|
|
Whether you need optimistic or pessimistic locking depends on the type of app you're trying to develop more than the DB tech you are using.
If the app is the type where the user performs an action and the DB records the result then you probably don't need a locking strategy.
If the app is more like CRUD screens where the user queries the stored data and then modifies it how they way then you probably do need a locking strategy.
Oracle forms, which favours CRUD, implements pessimistic locking by default for a reason.
|
|
|
|
|
|
|
|
|
|
|
Re: How to use Optimistic locking in my Package [message #673232 is a reply to message #673231] |
Mon, 12 November 2018 09:55 |
cookiemonster
Messages: 13920 Registered: September 2008 Location: Rainy Manchester
|
Senior Member |
|
|
BlackSwan wrote on Mon, 12 November 2018 15:27petar97 wrote on Mon, 12 November 2018 06:54I think it works as intended, but I think the procedure is way better
It also works to keep away vampires.
Stop being childish.
The triggers are a workable implementation of optimistic locking as far as I can see.
I've no idea why you, Michel and Ed think otherwise
Here, I'll demo:
Setup:
SQL> CREATE TABLE bob (ID NUMBER, b VARCHAR2(10), tcn NUMBER);
Table created
SQL> CREATE TRIGGER bob_bur
2 before update on bob FOR EACH ROW
3 begin
4 if(:NEW.TCN != :OLD.TCN+1) THEN
5 RAISE_APPLICATION_ERROR(-20000, 'Concurrency Failure');
6 end if;
7 :NEW.TCN := DBMS_UTILITY.GET_TIME;
8 END;
9 /
Trigger created
SQL> create or replace trigger bob_ibr
2 before insert on bob
3 for each row
4 begin
5 :NEW.TCN := DBMS_UTILITY.GET_TIME;
6 end;
7 /
Trigger created
SQL> insert into bob (id, b) values (1, 'text1');
1 row inserted
SQL> commit;
Commit complete
Case where session 1 reads and updates and then session 2 reads before session 1 commits:
Session 1:
SQL> select * from bob;
ID B TCN
---------- ---------- ----------
1 text1 429969331
SQL> update bob set b = 'text2', tcn = 429969331 + 1 where id = 1;
1 row updated
Session 2:
SQL> select * from bob;
ID B TCN
---------- ---------- ----------
1 text1 429969331
SQL> update bob set b = 'text3', tcn = 429969331 + 1 where id = 1;
<<HANGS>>
Session 1:
SQL> commit;
Commit complete
Session 2:
update bob set b = 'text3', tcn = 429969331 + 1 where id = 1
*
ERROR at line 1:
ORA-20000: Concurrency Failure
ORA-06512: at "LIVE.BOB_BUR", line 3
ORA-04088: error during execution of trigger 'LIVE.BOB_BUR'
Case where session 1 reads then session 2 reads, then session 1 updates:
Session 1:
SQL> select * from bob;
ID B TCN
---------- ---------- ----------
1 text2 429978188
Session 2:
SQL> select * from bob;
ID B TCN
---------- ---------- ----------
1 text2 429978188
Session 1:
SQL> update bob set b = 'text4', tcn = 429978188 + 1 where id = 1;
1 row updated
SQL> commit;
Commit complete
Session 2:
SQL> update bob set b = 'text5', tcn = 429978188 + 1 where id = 1;
update bob set b = 'text5', tcn = 429978188 + 1 where id = 1
*
ERROR at line 1:
ORA-20000: Concurrency Failure
ORA-06512: at "LIVE.BOB_BUR", line 3
ORA-04088: error during execution of trigger 'LIVE.BOB_BUR'
Now if we remove the trigger and run those updates, all 4 will succeed and the values session 1 wrote for B will be immediately overwritten by session 2's values.
That's a lost update.
Now for some apps that's fine, for others it isn't, and that distinction has nothing do to with whether the DB is oracle or SQL server.
That said, the procedure version negates the need for the triggers.
|
|
|
Re: How to use Optimistic locking in my Package [message #673236 is a reply to message #673229] |
Mon, 12 November 2018 14:39 |
|
EdStevens
Messages: 1376 Registered: September 2013
|
Senior Member |
|
|
cookiemonster wrote on Mon, 12 November 2018 08:37Why wouldn't it work as intended?
It may or may not. I have not dug into the actual code and thought through the implications. But given the OP history, I'm guessing that he thinks if it doesn't return an actual error, then it "works". I was suggesting he thing through what constitutes "it works".
Back when I was a cobol programmer, I worked with a guy that though that if he got his program to compile without error, then it was production ready.
[Updated on: Mon, 12 November 2018 14:53] Report message to a moderator
|
|
|
Re: How to use Optimistic locking in my Package [message #673243 is a reply to message #673236] |
Tue, 13 November 2018 05:02 |
cookiemonster
Messages: 13920 Registered: September 2008 Location: Rainy Manchester
|
Senior Member |
|
|
Given the OP posted this earlier:
"[that code is supposed] to get an error when something has changed in the table"
I see zero reason to think the OP thinks like your idiot ex-colleague and every reason to think he's actually tested it to see if it throws the error when expected.
And the phrasing of your previous post (emphasis on NOT) made it sound like you knew there was a problem with it (at least it does to me).
You've gone and filtered your view of this thread through the filter of the OPs previous threads and thus completely missed that he's come up with two different working implementations of optimistic locking.
The OP is lot smarter than you and the others think he is.
This bit is for everyone, not just you:
This thread is a mess of everyone other than the OP missing the point.
Oracles approach to locking does not mean that applications that use an oracle DB never have to implement optimistic locking.
The fact that the OP comes from a SQL Server background (where apparently you need to do a lot more optimistic locking) doesn't in anyway prove that what he's working on right now doesn't need optimistic locking.
|
|
|
Re: How to use Optimistic locking in my Package [message #673278 is a reply to message #673243] |
Wed, 14 November 2018 03:57 |
gazzag
Messages: 1118 Registered: November 2010 Location: Bedwas, UK
|
Senior Member |
|
|
This thread is fairly typical of this forum. A certain clique of our regular posters can be too quick to assume that someone is being "stupid" rather than trying to understand the OP. I don't think that this does them or this forum any credit. I appreciate that we can all get frustrated with posters who blatantly don't even try to learn but we should not make assumptions about someone just because we find it difficult to understand them. The fault can sometimes be ours. This forum might become a more useful resource to more people if we all remember that. There have been some good people on this forum who no longer post anymore. Does anyone wonder why?
Cookiemonster has demonstrated the concept of understanding perfectly in this thread.
|
|
|
Re: How to use Optimistic locking in my Package [message #673319 is a reply to message #673278] |
Wed, 14 November 2018 13:15 |
|
petar97
Messages: 120 Registered: October 2018
|
Senior Member |
|
|
I did not feel the need to answer why I needed a lock, but before that I said that I was doing it as an exercise, but I read the lock documentation.
And then it was stupid to answer some questions that did not lead me to a solution,
At least it seems to me.
Nowhere in the documentation did I find how to implement locking.
Otherwise I would not ask the question here.
Here's an example of why locking should be used.
What if a user takes money from the account twice at the same time, e.g. from ATMs or on the Internet
[Updated on: Wed, 14 November 2018 13:16] Report message to a moderator
|
|
|
|
Re: How to use Optimistic locking in my Package [message #673323 is a reply to message #673319] |
Thu, 15 November 2018 01:01 |
John Watson
Messages: 8931 Registered: January 2010 Location: Global Village
|
Senior Member |
|
|
Are you missing the whole point of optimistic/pessimistic locking? It is not related to transactional integrity. Oracle guarantees that, without you needing to do anything. So your example of two concurrent withdrawals from the same account is not a problem. Forget it.
Optimistic/pessimistic locking is all at the application level, when designing the user interface. Imagine you retrieve a set of rows, and look at them for a while. Then you update one. Do you want to lock the rows for the whole procedure (thus causing a problem for other users who might want to update them while you were twiddling your thumbs) or do you not want to lock them (thus causing a problem for yourself in that you won't be aware of any changes done by others while you were twiddling your thumbs)?
[Updated on: Thu, 15 November 2018 01:03] Report message to a moderator
|
|
|
Re: How to use Optimistic locking in my Package [message #673328 is a reply to message #673323] |
Thu, 15 November 2018 03:30 |
cookiemonster
Messages: 13920 Registered: September 2008 Location: Rainy Manchester
|
Senior Member |
|
|
Your example, and John's earlier one about buying something and it runs out of stock, are both cases where the only affect other users/sessions/processes have on what you are doing is whether you are allowed to do it at all.
Consequently you don't really need optimistic or pessimistic locking at all. You just need to write code that will check the balance/stock level at the point in time of the transaction and if it's too low throw an error.
e.g.
CREATE OR REPLACE PROCEDURE change_balance(p_account_id, p_amount)
l_new_balance NUMBER;
BEGIN
UPDATE accounts
SET balance = balance + p_amount --p_amount will be negative for withdrawels)
WHERE coount_id = p_account_id
RETURNING balance INTO l_new_balance;
IF l_new_balance < 0 THEN
raise_application_error(-20001, 'Sorry you're overdrawn');
END IF;
END;
|
|
|
|
Re: How to use Optimistic locking in my Package [message #673333 is a reply to message #673332] |
Thu, 15 November 2018 06:37 |
cookiemonster
Messages: 13920 Registered: September 2008 Location: Rainy Manchester
|
Senior Member |
|
|
Let's talk about why my example works.
It works because the update itself calculates the new balance.
Say there is $50 in your account
And you are withdrawing $30 at an ATM
And at the exact same time someone has hacked your amazon account and is buying something for $30.
Now that update locks the row so that only that session can do anything with it until a commit or rollback occurs.
And even if the requests happen at the exact same time one of them is going to lock the row first and the other is going to have to wait.
So session 1 gets it first, updates the balance to 20 (50-30), doesn't throw an error and then commits.
Then session 2 can perform it's update - the balance is calculated as 20 minus 30, giving -10 and an error is thrown.
Now suppose the procedure didn't calculate the balance in the update, but did it before hand with a select:
CREATE OR REPLACE PROCEDURE change_balance(p_account_id, p_amount)
l_current_balance NUMBER;
l_new_balance NUMBER;
BEGIN
SELECT balance INTO l_current_balance
FROM accounts
WHERE account_id = p_account_id;
l_new_balance := l_current_balance + p_amount;
IF l_new_balance < 0 THEN
raise_application_error(-20001, 'Sorry you're overdrawn');
END IF;
UPDATE accounts
SET balance = l_new_balance
WHERE account_id = p_account_id;
END;
That'll be fine 99.99999% of the time.
But what happens when two sessions run it at the exact same time for the same account.
Using the scenario above:
Session 1 does the select and sees the balance is 50 and sets new balance to 20.
Session 2 does the select and sees the balance is 50 and sets new balance to 20.
Session 1 updates accounts to set new balance to 20.
Session 2 updates accounts to set new balance to 20.
And $30 has been created by the magic of code bugs.
So how do you avoid that?
You could use my original code.
Or you could have the select lock the record:
CREATE OR REPLACE PROCEDURE change_balance(p_account_id, p_amount)
l_current_balance NUMBER;
l_new_balance NUMBER;
BEGIN
SELECT balance INTO l_current_balance
FROM accounts
WHERE account_id = p_account_id
FOR UPDATE;
l_new_balance := l_current_balance + p_amount;
IF l_new_balance < 0 THEN
raise_application_error(-20001, 'Sorry you're overdrawn');
END IF;
UPDATE accounts
SET balance = l_new_balance
WHERE account_id = p_account_id;
END;
The FOR UPDATE locks the row, causing the second session to block at the point of the select and then it logically behaves the same as my original code.
Now that is short term pessimistic locking.
You could do this with optimistic locking if you really wanted to, but why bother.
Now supposed (for some bizarre reason) that the client worked out the new balance instead.
The ATM and amazon have both queried the current account balance, subtracted the amount they're taking off and send the new balance to the bank.
(Bank systems would never allow this, it would be fraud central).
Then to avoid the magically created money you need to implement optimistic/pessimistic locking.
Here's a better optimistic locking example:
At my work we use redmine to record our dev tasks - what bugs/enhancements we're working on and what we do to the code base.
Say my boss asks me to look at a bug.
He assigns the issue to me in redmine.
I have a look at the description and realize the problem is in the Java app, not the DB, and I'm not the best person to do anything about it.
So I turn to Java Dev Adam, who's sat next to me, and ask if he's busy. He's not, so I ask if he wants to take a look, he says yes and I assign it over to him.
At the same time I'm looking at it, my boss, who's not in earshot, realizes it's a Java bug and messages Java Dev Bobby to see if he's busy. He's not, so my boss updates the redmine to assign the issue to him. He hits the submit button a few seconds after I do.
And lo, Redmine says unto him:
"User cookiemonster has updated this issue since you started viewing it and has made the following changes, do you still wish to apply your changes?"
Then me and the boss have a chat, I tell Adam to forget about it, boss commits his change and we avoid the possibility of two devs investigating the same bug independently (yes there are email notifications, no not everyone always reads them).
Optimistic locking is best in situations where changes to the data by other users since you queried it may, or may not, cause you to do something different.
|
|
|
|
Re: How to use Optimistic locking in my Package [message #673416 is a reply to message #673323] |
Mon, 19 November 2018 09:18 |
cookiemonster
Messages: 13920 Registered: September 2008 Location: Rainy Manchester
|
Senior Member |
|
|
John Watson wrote on Thu, 15 November 2018 07:01Are you missing the whole point of optimistic/pessimistic locking? It is not related to transactional integrity. Oracle guarantees that, without you needing to do anything. So your example of two concurrent withdrawals from the same account is not a problem. Forget it.
Just re-read this.
No oracle doesn't automatically avoid the two concurrent withdrawal problem without you having to do anything.
Have a look at my examples in my previous post.
If you select the data to work out what you need without explicitly locking it you're going to have a problem.
|
|
|
|
|
|
|
|
|
|
|
|
|
|