cancel
Showing results for 
Search instead for 
Did you mean: 
Read only

writing in same table without locking it

Baron
Participant
0 Kudos
776

I am using sql anywhere 17, and get several connections concurrently trying to update the same row in a table.

Each connection per se does lot of calculations and at the end updates some specific rows in a table.

The problem is that sometimes different connections cause an update on the same row/s, so that they causes an deadlock, and no one of them can accomplish the job and commit.

Is there a simple way to solve such a problem withouth implementing isolation levels? since I dont have any experience with isolation levels.

Accepted Solutions (0)

Answers (2)

Answers (2)

VolkerBarth
Contributor
0 Kudos

As to your sample:

It shows the typical deadlock problem of trying to acquire the same resources in different (or here random) order (as there is no defined order in the cursor), and it also misses access on the underlying table before the update itself.

Here's a slightly enhanced procedure that joins the sa_split_list-results with the MyInventary table, returns the rows in a declared order (so in the same order for each transaction) and uses FOR UPDATE to make the server place an "intent lock" on each row. In a separate connection, you can use "call sa_locks()" to notice how the rows are locked before the updates are tried, and there shopuld be no dead lock. (Of course both sample miss error handling in case the quantity would get negative.)

It basically forces a transaction to get its own resources in the same order, and no transaction can "overtake" another one, so basically they are serialized if they try to access the same products.

 

create or replace procedure ReceiveSinglePiecesShipping(ProductList long varchar)
begin
for ShippedProducts as mycursor cursor for
   select MI.ProductName as MyProductName, Quantity
   from sa_split_list(ProductList) SPL inner join MyInventary MI on trim(SPL.row_value) = MI.ProductName
   order by MI.ProductName
   for update
do
   waitfor delay ('00:00:02');
   update MyInventary set Quantity = Quantity - 1 where ProductName = MyProductName;
   end for;
end;

 

 

VolkerBarth
Contributor
0 Kudos
Just to add: There are many aspects to locking, isolation levels, cursor types, and I certainly do not claim to know too much of all that...
Baron
Participant
0 Kudos

Hello Volker, thanks for the reply.
What is the goal of adding "Quantity" in the cursor definition?
Is it needed with the "For Update" clause? I dont have experience with this clause.

VolkerBarth
Contributor
0 Kudos
I'm not sure it is really needed (and the FOR UPDATE clause is mainly for positioned updates, which I have not used here), I just wanted to show how to block the row to be updated by querying it within the cursor. Just try whether it does work without the quantity column.
VolkerBarth
Contributor
0 Kudos

Well, you simply cannot write a row without locking it, writers do have to block other writers on the same row.

What do you mean by "deadlock" here? If transaction A updates row X and transaction B tries to update the same row (assuming A has already locked it), B will either have to wait to do its update (when option Blocking is "on" and A rollbacks/commits before B's blocking timeout has been exceeded) or it will fail. That's a failed transaction but no deadlock.
A deadlock arises when there are cyclic blocks (*), say, when A tries to update row Y which has already been updated by Y in Y's running transaction, so neither A nor B can succeed. Then the database server will rollback one to A or B to resolve the deadlock.
The classical text book approach to prevent such deadlocks is either to acquire resources in the same order, say by trying to update rows in the same PK order, or to make each transaction work on their separate area of resources.

(*) - There are other causes for deadlocks, e.g. running out of worker threads.

Baron
Participant
0 Kudos
Hello Volker, glad to read from you again, I was excited whether I was posting in the correct place and whether you will see it.
Baron
Participant
0 Kudos
Hello Volker, glad to read from you again, I was excited whether I was posting in the correct place and whether you will see it. In my case it is deadlock since both connections come with a list of rows and apparently with different order so that connection1 tries to lock row X and then row Y, connection2 tries maybe row Y and then row X. The commit happens apparently at the end so X&Y build a deadlock.
Baron
Participant
0 Kudos
Here is what ChatGPT tells about the problem: https://chatgpt.com/share/673aece4-7218-8010-adb1-db2ec2b06c57
VolkerBarth
Contributor
0 Kudos
Well, I won't comment on whether this is the "right place" to be - apparently SAP has made sure there is no other. 😕
Baron
Participant
0 Kudos
with "right place" I was meaning whether I could choose the correct category (among the big range of SAP topics), I have this problem while reading old posts and also while posting a new question.
VolkerBarth
Contributor
0 Kudos

I don't know whether it is worthwhile to discuss AI recommendations – apparently you can try those suggestions with a "staging table".

Personally, I'd favour a solution without separate "helper tables", where each transaction

  • either just updates a single row (so using many short transactions to prevent race conditions),
  • or "marks" those rows it is about to update (by explicitly blocking those against other writers, or by updating a "flag" on that row saying "I'm about to doing more work here"). Another transaction would then notice that it cannot block these rows itself, would skip over them and could "mark" its own set of rows.
Baron
Participant
0 Kudos

Here is an example how I can regenerate the error which I am getting in production:

create or replace table MyInventary (ProductName varchar(10) primary key, Quantity int);
insert into MyInventary values ('Mouse', 10), ('Keyboard', 20), ('Ram', 100), ('HDD', 15);
create or replace procedure ReceiveSinglePiecesShipping(ProductList long varchar)
begin
for ShippedProducts as mycursor cursor for select row_value as ProductInCursor from sa_split_list(ProductList) do
waitfor delay ('00:00:01');
update MyInventary set Quantity = Quantity - 1 where ProductName = trim(ProductInCursor) ;
end for;
end;

 

And then I call the procedure from two separate connections:
Connection1: call ReceiveSinglePiecesShipping('Mouse,Keyboard,RAM,Mouse');commit;
Connection2: call ReceiveSinglePiecesShipping('HDD, RAM, Mouse,Keyboard,RAM');commit;

I get a deadlock with the second connection and it is rollbacked.