Skip to Main Content
  • Questions
  • user with select-only privs can lock another schema's table?

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, neil.

Asked: February 14, 2006 - 4:36 pm UTC

Last updated: February 17, 2006 - 1:16 pm UTC

Version: 9.2

Viewed 1000+ times

You Asked

I have a case where user NEIL has SELECT privs on CUBS_OWNER.state_codes. NEIL also has resource and create session



SQL> show user
USER is "NEIL"

SQL> select * from user_sys_privs;

USERNAME PRIVILEGE ADM
---------- ------------------------------ ---
NEIL CREATE SESSION NO
NEIL UNLIMITED TABLESPACE NO

SQL> select * from user_role_privs;

USERNAME GRANTED_ROLE ADM DEF OS_
---------- ------------------------------ --- --- ---
NEIL CONNECT NO YES NO
NEIL RESOURCE NO YES NO

SQL> select * from all_tab_privs where table_name='STATE_CODES' and table_schema='CUBS_OWNER';

GRANTOR GRANTEE TABLE_SCHEMA TABLE_NAME PRIVILEGE GRA HIE
--------------- ---------- --------------- --------------- ---------- --- ---
CUBS_OWNER NEIL CUBS_OWNER STATE_CODES SELECT NO NO

SQL> update cubs_owner.state_codes set name=name;
update cubs_owner.state_codes set name=name
*
ERROR at line 1:
ORA-01031: insufficient privileges

However, as NEIL, I can do a SELECT * FROM cubs_owner.state_codes FOR UPDATE;

SQL> select * from cubs_owner.state_codes for update;

ST NAME CNT CREATED_D MODIFIED_ WG_ALLOW_IND ST CREATED_BY
-- -------------------- --- --------- --------- ------------ -- ----------
MODIFIED_BY BEG_DT END_DT
----------- --------- ---------
AK Alaska 31-MAR-00 1 02 10
01-APR-90

AL Alabama 31-MAR-00 1 01 10
01-APR-90
<SNIP>
63 rows selected.


Now, in another session, as cubs_owner, I try to alter table state_codes and get the error that the table is locked

SQL> show user
USER is "CUBS_OWNER"

SQL> alter table state_codes add (dummyfield varchar2(256));
alter table state_codes add (dummyfield varchar2(256))
*
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified


So, my question is, how can a user with SELECT only privileges lock the table and prevent the schema owner from making changes?



and Tom said...

Yes, this is 'known' and expected - and I agree that not many people are aware of this (that is the primary reason I've decided to print this one, to raise awareness). The documentation for the lock table command for example:

</code> http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9015.htm#sthref7368 <code>

Prerequisites:

The table or view must be in your own schema or you must have the LOCK ANY TABLE system privilege, or you must have any object privilege on the table or view.

Note that does not convey the ability to "modify" data - but the ability to read data
does convey the ability to "lock" - stabilize - freeze - disallow modification
to - that data. SELECT ... FOR UPDATE works the same way. This might raise the question "why is this true".

Well, one scenario is as follows. Suppose I have read access to the conference room table and I have read/write access to the conference schedule table. I would like to schedule conference room "X". In order to do this safely (to avoid overlaps) no one else can schedule conference room "X". Therefore, I need to "lock" conference room "X" (the transaction that schedules rooms is a stored procedure as it should be - all attempts to modify the conference schedule table is via this procedure). It does a select for update on the conference room - locking it - and making it safe to peek in the schedules table to see if anyone else has it reserved.

Another would be that I would like to delete from table P. P is the parent of C. I have read/write acccess to P. I have read only access to C. I attempt to delete from P - before I can do that, I need to lock a bit of C (if indexed fkeys exist) or all of C (no index on fkey).


Rating

  (6 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Enhancement filed

Duke Ganote, February 15, 2006 - 11:28 am UTC

As always, a true Jedi

Khalid, February 15, 2006 - 12:42 pm UTC

Thanks for the two scenarios where you need the locks.
I have a need for a lock (serialization) inside a trigger myself for a very very short duration, but I don't know how to do it, would you please look at my problem?

I need a serialization around two count(*)'s, but select for update won't work in my case. I need this inside a trigger.

The error I get is
Select count(*) from my_table for update won't work.
ERROR at line 1:
ORA-01786: FOR UPDATE of this query expression is not allowed

Here is my trigger, I ha

create or replace trigger qrs.room_full_alert_iufer
after insert or update on qrs.rooms
for each row
declare
v_rm_count number;
v_chs_count number;
begin

if inserting or updating then

-- Need some serialization/some locking mechanism here

select count(*) into v_rm_count from qrs.prtcpnt_rooms a
where a.room__id = :new.room__id;

select count(*) into v_chs_count from qrs.room_held_seats a
where a.room_id = :new.room_id;

if :new.max_enrolment > (v_rm_count + v_chs_count) then
v_full := 'N';
else
v_full := 'Y';
end if;

-- End serialization here

exception
when others then
dbms_output.put_line(sqlcode || ' ' || sqlerrm);
raise;
end qrs.room_full_alert_iufer;

Thanks again in advance.



Tom Kyte
February 15, 2006 - 1:17 pm UTC

there must be a parent table somewhere with a primary key of room_id right?

select for update THAT ROW.

You will therefore serialize at the ROOM_ID level.


And just make sure your application runs in read committed isolation only :)

you need to serialize your transaction. not just that "critical section"


and please, erase that when others, it is less than pointless. It is less than useful. The only thing it does is hide from the client where the actual error took place - nothing else.



select count(*) into v_rm_count from qrs.prtcpnt_rooms a
where a.room__id = :new.room__id;

select count(*) into v_chs_count from qrs.room_held_seats a
where a.room_id = :new.room_id;

if :new.max_enrolment > (v_rm_count + v_chs_count) then

could be


select case when :new.max_enrolment > cnt
then 'N'
else 'Y'
end into whatever
from (select sum(cnt)
from (select count(*) cnt from t1 where ...
union all
select count(*) cnt from t2 where ... )
)



My question was answered, and I got something more

Khalid, February 15, 2006 - 1:53 pm UTC

Yes there is a parent table, where room_id is the primary key. I will test my code with the solution you have provided.

Thanks for the reminder on the 'when others ... ' also, I will try not to do error handling/propogation like that again.

Tom Kyte
February 15, 2006 - 2:13 pm UTC

no need to thanks on the "when others"

it is right up on there on the list of automatic knee jerk responses - right after "use bind variables"

Where shall I asktom?

Khalid, February 15, 2006 - 4:39 pm UTC

Tom, I am now reading a different thread namely,
</code> http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:1155066278457 <code>

If I have some questions about that thread, where shall I ask this question, and what is your preference?
1) This thread? knowing that this is an OT question
2) exceptions thread?
3) neither

Tom Kyte
February 15, 2006 - 9:52 pm UTC

well, if you have a question about the contents of that question, the most logical place to ask it would be - that question.

how could it be OT (off topic?) if it is about that question/answer thread?



Solution works, but I am missing the concept.

Khalid, February 16, 2006 - 12:23 pm UTC

You gave me a great solution, but I am somehow still not getting this concept clear. I knew you discuss some of this in your book 1, I read it again, but still ...

Notice you constructed a single single SQL statement for me, so the data should be read consistent for the entire life time of this statement, therefore my question to you is:

What confuses me is the following:-
a) "And just make sure your application runs in read committed isolation only :)"
b) 'you need to serialize your transaction. not just that "critical section"'

So the below solution serializes by virtue of wrapping the entire critical section within a single statement?
and I am assuming Oracle provides REPEATABLE READ by default will give me the serialization at the statement level automatically. There is nothing more than that I need.

But setting READ COMMITTED ONLY as you say, I have to provide serialization not just for the statement but for the entire duration of the transaction.

-- THE SQL YOU GAVE ME ALL DONE IN 1 STATEMENT ------
-- WHICH I THINK IS SUFFICIENT BY ITSELF ------
select case when :new.max_enrolment > cnt
then 'N'
else 'Y'
end into whatever
from (select sum(cnt)
from (select count(*) cnt from t1 where ...
union all
select count(*) cnt from t2 where ... )
)

Tom Kyte
February 17, 2006 - 1:16 pm UTC

when you run in serializable, all queries you run will run "as of the time the transaction began", you won't SEE ANY CHANGES made by other concurrent sessions.


You need to SELECT FOR UPDATE the single PARENT record (that will serialize)

and then you can read the other table(s).




Will do that

Khalid, February 17, 2006 - 10:47 pm UTC


More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library