Home » SQL & PL/SQL » SQL & PL/SQL » select first unlock record in table for update (Oracle 10gR2)  () 1 Vote
select first unlock record in table for update [message #556800] Wed, 06 June 2012 11:44 Go to next message
belf
Messages: 11
Registered: June 2012
Junior Member
Hi all,

I'm looking for a solution to select the first row that is not currently locked in a table and insert a record to another table that reference that first row. this is my scenario:

create table ticket
(
   id           number(10)  not null,,
   ticket_type  number(1)  not null,,
   is_sold      number(1) not null,
   CONSTRAINT ticket_pk PRIMARY KEY (id)
); 
	
create table customer_ticket
(
   cust_id     number(10),
   cust_name   varchar2(50)
   ticket_id   number(1),
   CONSTRAINT  ticket_fk 
   FOREIGN KEY (ticket_id) 
   REFERENCES ticket(id) 
); 	
	 
SQL> select * from ticket;

    id            ticket_type     is_sold
------------ -------------------- -----
10000004            1               1 
10000005            2               1 
10000006            1               0
10000007            1               0
10000008            2               0
10000009            2               0


SQL> select * from customer_ticket;

  cust_id        cust_name         ticket_id
------------ -------------------- ----------
1                    John          10000004 
2                    Sara          10000005



my goal is finding the first free ticket ( not sold ) in the ticket table and insert buyer information of that ticket in customer_ticket table. at last I will mark that ticket as a sold one in ticket table with update.
Problem is that the first transaction locks the the first row in ticket table and the second transaction running the same query goes to wait untill the first transaction commit or rollback. However when first transaction finish successfully, second transaction select duplicate id from ticket table that was selected by the first transaction!

I tried to solve problem with "skip locked" and "nowait" options with select for update, but they didn't help.

Thank you for any help...

Regards,
Belf
Re: select first unlock record in table for update [message #556801 is a reply to message #556800] Wed, 06 June 2012 12:09 Go to previous messageGo to next message
BlackSwan
Messages: 26766
Registered: January 2009
Location: SoCal
Senior Member
>I'm looking for a solution to select the first row that is not currently locked in a table
rows in a table are like balls in a basket.
Which ball in a basket is the "first" ball?
Why must select "first" row?
Why not select any unlock row?
How does second session know, decide or determine, which other rows are locked?
Re: select first unlock record in table for update [message #556802 is a reply to message #556801] Wed, 06 June 2012 12:33 Go to previous messageGo to next message
belf
Messages: 11
Registered: June 2012
Junior Member

Dear BlackSwan,

I use min(id) in ticket table for selecting first row and business of company forces the tickets are sold in order. this is my query for selecting first free ticket:

open mycursor for
select id,ticket_type,is_sold 
from ticket where id = ( select min(id) from ticket where ticket_type=1 and is_sold=0);
for update


when this query runs from session 1, it selects id = 10000006 and locks that row for insert and update. when session 2 run the same query, it goes to wait until the session 1 commit or rollback. although session 1 commits its work but session 2 select id = 10000006 !
Re: select first unlock record in table for update [message #556803 is a reply to message #556802] Wed, 06 June 2012 12:46 Go to previous messageGo to next message
joy_division
Messages: 4963
Registered: February 2005
Location: East Coast USA
Senior Member
I'm not sure I FULLY understand if you have a problem or not based on your description. You still didn't say how you lock a row. Is it an Oracle lock or some code you "think" locks it.

Have a look at LOCK with NOWAIT.
Re: select first unlock record in table for update [message #556809 is a reply to message #556803] Wed, 06 June 2012 13:17 Go to previous messageGo to next message
andrew again
Messages: 2577
Registered: March 2000
Senior Member
I'm surprised that you're using pesimistic locking rather than optimistic - it's very old-fashioned for many reasons and is generally unsuitable for web apps.

You can skip over locked records like this ==> http://www.orafaq.com/forum/m/522598/45693/#msg_522598

An alternative would be to logically lock records for some pre-determined period. Say your business rule allows a record to only be locked for 5 minutes, then you'd update the record bing worked on with the userid and sysdate and then commit. You'd probably also keep pushing out the lock timestamp as long as the user is still working the record. When the timstamp is older than 5 min, then it's available to be tagged and logically locked by another user. Finding unlocked records then becomes trivial based on the lock timestamp.

[Updated on: Wed, 06 June 2012 13:19]

Report message to a moderator

Re: select first unlock record in table for update [message #556810 is a reply to message #556803] Wed, 06 June 2012 13:17 Go to previous messageGo to next message
belf
Messages: 11
Registered: June 2012
Junior Member
This is my simplified code:


function sellTicket(I_ticket_type             IN number,
                    I_cust_id                 IN number,
		    I_cust_name               IN varchar2
                  ) return number is
   
      v_mycursor ref cursor;;
      vr_mycursor ticket%rowtype;
      vt_return  NUMBER := 0;
begin 
	  open mycursor for
       select id,ticket_type,is_sold 
       from ticket where id = ( select min(id) from ticket where ticket_type= I_ticket_type and is_sold=0);
       for update;
  
    fetch v_mycursor
      into vr_mycursor;
    if v_mycursor%notfound then
      Rollback;
      vt_return := 2;
    else
      insert into customer_ticket
        (cust_id,
         cust_name,
         ticket_id )
      values
        (I_cust_id,
         I_cust_name,
	 vr_mycursor.id);
      
	  update ticket o set o.is_sold = 1 
         where o.id = vr_mycursor.id;
         vt_return := 0;
         
      commit;
    end if;
    RETURN vt_return;
  Exception
    when DUP_VAL_ON_INDEX then
      Rollback;
      return 23;
    when others then
       raise_application_error(-20000, sqlerrm);
  
  end sellTicket;



I run this function from 2 different sessions. session 2 waits on " open mycursor for..." until committing the operation in session 1.
I check with "nowait" option but it raise application error and session 2 couldn't assign ticket to customer. I also checked with "skip locked" option but result is " if v_mycursor%notfound then ... vt_return := 2;". I expect session 2 can select another available free ticket in table.

I hope this make the problem more clear.

Regards,
Belf


Re: select first unlock record in table for update [message #556840 is a reply to message #556810] Wed, 06 June 2012 23:18 Go to previous messageGo to next message
Michel Cadot
Messages: 68765
Registered: March 2007
Location: Saint-Maur, France, https...
Senior Member
Account Moderator
I don't read the purpose of this procedure but I already see several bugs:
- "Rollback/commit" NO, never in a procedure, ONLY the caller knows if the work does by the procedure (and the previous work) has to be rolled bacn or commited, NOT the procedure
- "when others then raise_application_error(-20000, sqlerrm);" this simply STUPID, why do you think it does more than if you don't write it? To know what it does less, why it is an error read WHEN OTHERS.
- You open but do not close the cursor
- Why use a ref cursor and not a static cursor?

Regards
Michel

Re: select first unlock record in table for update [message #556916 is a reply to message #556840] Thu, 07 June 2012 05:01 Go to previous messageGo to next message
belf
Messages: 11
Registered: June 2012
Junior Member
Dear Michel

Thank you for your response. I am sorry if I didn't explain the problem properly. Plz, follow the post to get your response.

Quote:

"Rollback/commit" NO, never in a procedure, ONLY the caller knows if the work does by the procedure (and the previous work) has to be rolled bacn or commited, NOT the procedure


I understand using commit inside a procedure would defeat the basic definition of an atomic transaction and The application that calls the stored procedure should start and commit/rollback the transaction, but in our company the situation is relatively different. Application connects to multiple databases and some of these databases use commit/rollback in their procedure and removing these commits/rollbacks from those are not possible now ( these databases have many packages including many procedures inside them and modifying them is so time-consuming ). So, I should handle commit/rollback inside my code as others.

Quote:

"when others then raise_application_error(-20000, sqlerrm);" this simply STUPID, why do you think it does more than if you don't write it? To know what it does less, why it is an error read WHEN OTHERS.

about using "when others" exception, you are right it is mistake. In fact, I don't throw the exception to application and I catch it and instead return a value to application. So, I do first rollback the operation in "when others" and then return value.

Quote:
You open but do not close the cursor


You are right, I should close it.

Quote:

Why use a ref cursor and not a static cursor?


As I mentioned earlier, this code is simplified. I have several other inputs in my functions that affect my where condition in selecting unsold ticket. Each of these input may be null or not, So I check them by if statement and each null input will be removed from where condition. However, if I want to use static cursor in replace of ref cursor, I should declare several static cursors in my function while only one of them every time I use.

Regards,
Belf
Re: select first unlock record in table for update [message #556925 is a reply to message #556916] Thu, 07 June 2012 05:46 Go to previous messageGo to next message
Michel Cadot
Messages: 68765
Registered: March 2007
Location: Saint-Maur, France, https...
Senior Member
Account Moderator
Quote:
In fact, I don't throw the exception to application and I catch it and instead return a value to application. So, I do first rollback the operation in "when others" and then return value.


If you don't catch it then:
1/ The operation is rolled back
2/ The caller knows the EXACT error
3/ The caller knows what and where the error comes from
Read the link I posted.

Quote:
However, if I want to use static cursor in replace of ref cursor, I should declare several static cursors in my function while only one of them every time I use.


Read "Cursor Versus Ref Cursor" at http://www.oracle.com/technetwork/issue-archive/2012/12-may/o32asktom-1578049.html

Regards
Michel
Re: select first unlock record in table for update [message #556940 is a reply to message #556800] Thu, 07 June 2012 07:36 Go to previous messageGo to next message
Solomon Yakobson
Messages: 3305
Registered: January 2010
Location: Connecticut, USA
Senior Member
belf wrote on Wed, 06 June 2012 12:44
I tried to solve problem with "skip locked" and "nowait" options with select for update, but they didn't help.


Obviously, SKIP LOCKED will not help the way you wrote a query. SELECT always sees all rows, therefore subquery:

select min(id) from ticket where ticket_type=1 and is_sold=0


will return same is for both sessions. So if session 1 has id selected for update,session 2 without SKIP LOCKED will wait and with SKIP LOCKED will not return any rows since it will skip row locked by session 1 and there are no more rows with min(id). What you need is loop through select all rows in table ticket ordered by ID and then trying to select that row FOR UPDATE SKIP LOCKED. If row is locke and therefore skipped query will raise NO_DATA_FOUND which you catch and mask. Otherwise it is the row you are looking for. For example:

-- Session 1
declare
    cursor v_cur
      is
        select  id
          from  ticket 
          where ticket_type=1
            and is_sold=0
          order by id;
    v_id number;
begin
    for v_rec in v_cur loop
      begin
          select  id
            into  v_id
            from  ticket
            where id = v_rec.id
            for update
            skip locked;
        exception
          when no_data_found
            then
              dbms_output.put_line('ID = ' || v_rec.id || ' is locked.');
      end;
      exit when v_id is not null;
    end loop;
    if v_id is null
      then
        dbms_output.put_line('No qualifying tickets.');
      else
        dbms_output.put_line('ID = ' || v_id);
    end if;
end;
/
ID = 10000006

PL/SQL procedure successfully completed.

SQL> 


And

-- Session 1
declare
    cursor v_cur
      is
        select  id
          from  ticket 
          where ticket_type=1
            and is_sold=0
          order by id;
    v_id number;
begin
    for v_rec in v_cur loop
      begin
          select  id
            into  v_id
            from  ticket
            where id = v_rec.id
            for update
            skip locked;
        exception
          when no_data_found
            then
              dbms_output.put_line('ID = ' || v_rec.id || ' is locked.');
      end;
      exit when v_id is not null;
    end loop;
    if v_id is null
      then
        dbms_output.put_line('No qualifying tickets.');
      else
        dbms_output.put_line('ID = ' || v_id);
    end if;
end;
/
ID = 10000006 is locked.
ID = 10000007

PL/SQL procedure successfully completed.

SQL> 


SY.

[Updated on: Thu, 07 June 2012 07:37]

Report message to a moderator

Re: select first unlock record in table for update [message #556957 is a reply to message #556940] Thu, 07 June 2012 10:37 Go to previous messageGo to next message
belf
Messages: 11
Registered: June 2012
Junior Member
Many Thanks Syakobson for help. It work well Smile
I understand skip locked is an undocumented feature in oracle 10g, so it may not be safe for the general cases. Can you help me to find another way to solve the problem?

Regards
Belf
Re: select first unlock record in table for update [message #556962 is a reply to message #556957] Thu, 07 June 2012 11:01 Go to previous messageGo to next message
Solomon Yakobson
Messages: 3305
Registered: January 2010
Location: Connecticut, USA
Senior Member
Sure. Use NOWAIT instead. Session1:

declare
    v_resource_busy exception;
    pragma exception_init(v_resource_busy,-54);
    cursor v_cur
      is
        select  id
          from  ticket 
          where ticket_type=1
            and is_sold=0
          order by id;
    v_id number;
begin
    for v_rec in v_cur loop
      begin
          select  id
            into  v_id
            from  ticket
            where id = v_rec.id
            for update
            nowait;
        exception
          when v_resource_busy
            then
              dbms_output.put_line('ID = ' || v_rec.id || ' is locked.');
      end;
      exit when v_id is not null;
    end loop;
    if v_id is null
      then
        dbms_output.put_line('No qualifying tickets.');
      else
        dbms_output.put_line('ID = ' || v_id);
    end if;
end;
/
ID = 10000006

PL/SQL procedure successfully completed.

SQL> 


Session 2:

declare
    v_resource_busy exception;
    pragma exception_init(v_resource_busy,-54);
    cursor v_cur
      is
        select  id
          from  ticket 
          where ticket_type=1
            and is_sold=0
          order by id;
    v_id number;
begin
    for v_rec in v_cur loop
      begin
          select  id
            into  v_id
            from  ticket
            where id = v_rec.id
            for update
            nowait;
        exception
          when v_resource_busy
            then
              dbms_output.put_line('ID = ' || v_rec.id || ' is locked.');
      end;
      exit when v_id is not null;
    end loop;
    if v_id is null
      then
        dbms_output.put_line('No qualifying tickets.');
      else
        dbms_output.put_line('ID = ' || v_id);
    end if;
end;
/
ID = 10000006 is locked.
ID = 10000007

PL/SQL procedure successfully completed.

SQL> 


SY.
Re: select first unlock record in table for update [message #556973 is a reply to message #556962] Thu, 07 June 2012 13:41 Go to previous messageGo to next message
belf
Messages: 11
Registered: June 2012
Junior Member
Solomon,

The example you have given using nowait to select the first row that is not currently locked is
exactly what I am looking for. Thanks again for your reply.

Thanks other guys for their helpful comments,too.

Good luck,
Belf
Re: select first unlock record in table for update [message #557909 is a reply to message #556800] Sun, 17 June 2012 13:00 Go to previous message
mr_wee
Messages: 18
Registered: June 2011
Junior Member

I have a question: if the order of tickets wouldn't be important and we want just to find a free ticket in table, is there another solution that does not select all free tickets in cursor and then fetch rows one by one in the loop?

Regards,
Wee
Previous Topic: Query to return value/string if no records found
Next Topic: Group by with hierarchy
Goto Forum:
  


Current Time: Fri Aug 08 12:02:47 CDT 2025