Reputation: 28889
I have two tables, foo
and bar
:
foo
,----------------.
|id | name | ... |
|---+------+-----|
| 1 | abc | ... |
| 2 | xyz | ... |
| ... |
`----------------'
bar
,-------------------------.
| id | foo_id | num | ... |
|----+--------+-----+-----|
| 1 | 1 | 1 | ... |
| 2 | 1 | 2 | ... |
| 3 | 1 | 3 | ... |
| 4 | 2 | 1 | ... |
| 5 | 2 | 2 | ... |
| ... |
`-------------------------'
For each foo
record, there are many bar
records, each with a slot number (num
), which should be a value from 1 - N for each foo_id
. Thus, there's a unique key on (foo_id
, num
). These slot numbers are meaningful, and must be sequential.
Because of that last constraint, I can't use Oracle's native sequences (as they aren't guaranteed to be sequential, and don't seem to be meant for values that are meaningful). The number of foo
records is also extermely large, and having a sequence for each foo_id
seems highly impractical anyway.
So, my question is - what approaches exist for creating these numbers on insertion that would hold up under high concurrency? Is my only option to lock and select MAX(num) + 1
, and release the lock on commit?
Upvotes: 0
Views: 143
Reputation: 4694
you can use a 2 step aproach:
run a script that will populate num out of sequence numbers:
--q1
insert into bar (id, foo_id, seq_num) values (:id, :foo, someseq.next_val)
--q2
update bar b1 set
num = (select 1+count(*) from bar b2
where b2.foo_id = b1._foo_id
and b2.seq_num < b1.seq_num)
where num is null
Or you could have bar with gaps, but then have a view bar_vw that would be correcting it on a fly:
create view bar_vw as
select id, foo_id,
row_number() over (partition by foo_id, order by seq_num) as num
from bar
Upvotes: 2
Reputation: 231661
If there is a way to architect the system such that only one session at a time is inserting bar
rows for any particular foo
row, that would be ideal since that would eliminate actual contention (I assume you'd still want to code to handle it just in case).
To manage concurrency, you'd almost certainly need to lock the parent foo
row (select for update
) and then do the max(num) + 1
from the bar
rows with that foo_id
. I suppose you could potentially omit the step of locking the foo
record and create a materialized view that does a fast refresh on commit to validate that the num
values are sequential but it seems highly unlikely that this would be faster and it seems highly likely that it would make the code more complex since you're now catching exceptions at commit time rather than simply blocking until you can move forward.
Upvotes: 0