【发布时间】:2019-07-19 15:42:14
【问题描述】:
假设有两个表TST_SAMPLE (10000 rows) 和TST_SAMPLE_STATUS (empty)。
我想遍历TST_SAMPLE 中的每条记录,并相应地将一条记录添加到TST_SAMPLE_STATUS。
在一个简单的线程中:
begin
for r in (select * from TST_SAMPLE)
loop
insert into TST_SAMPLE_STATUS(rec_id, rec_status)
values (r.rec_id, 'TOUCHED');
end loop;
commit;
end;
/
在多线程解决方案中有一种情况,我不清楚。
那你能解释一下是什么原因导致处理一行TST_SAMPLE多次。
请参阅下面的详细信息。
create table TST_SAMPLE(
rec_id number(10) primary key
);
create table TST_SAMPLE_STATUS(
rec_id number(10),
rec_status varchar2(10),
session_id varchar2(100)
);
begin
insert into TST_SAMPLE(rec_id)
select LEVEL from dual connect by LEVEL <= 10000;
commit;
end;
/
CREATE OR REPLACE PROCEDURE tst_touch_recs(pi_limit int) is
v_last_iter_count int;
begin
loop
v_last_iter_count := 0;
--------------------------
for r in (select *
from TST_SAMPLE A
where rownum < pi_limit
and NOT EXISTS (select null
from TST_SAMPLE_STATUS B
where B.rec_id = A.rec_id)
FOR UPDATE SKIP LOCKED)
loop
insert into TST_SAMPLE_STATUS(rec_id, rec_status, session_id)
values (r.rec_id, 'TOUCHED', SYS_CONTEXT('USERENV', 'SID'));
v_last_iter_count := v_last_iter_count + 1;
end loop;
commit;
--------------------------
exit when v_last_iter_count = 0;
end loop;
end;
/
在FOR-LOOP 中,我尝试遍历以下行:
- 没有状态(NOT EXISTS 子句)
- 当前未锁定在另一个线程中(FOR UPDATE SKIP LOCKED)
对于迭代中的确切行数没有要求。
这里pi_limit 只是一个批次的最大尺寸。唯一需要做的就是在一个会话中处理TST_SAMPLE 的每一行。
所以让我们在 3 个线程中运行这个过程。
declare
v_job_id number;
begin
dbms_job.submit(v_job_id, 'begin tst_touch_recs(100); end;', sysdate);
dbms_job.submit(v_job_id, 'begin tst_touch_recs(100); end;', sysdate);
dbms_job.submit(v_job_id, 'begin tst_touch_recs(100); end;', sysdate);
commit;
end;
出乎意料的是,我们看到一些行在多个会话中处理
select count(unique rec_id) AS unique_count,
count(rec_id) AS total_count
from TST_SAMPLE_STATUS;
| unique_count | total_count |
------------------------------
| 10000 | 17397 |
------------------------------
-- run to see duplicates
select *
from TST_SAMPLE_STATUS
where REC_ID in (
select REC_ID
from TST_SAMPLE_STATUS
group by REC_ID
having count(*) > 1
)
order by REC_ID;
请帮助识别程序执行过程中的错误tst_touch_recs。
【问题讨论】:
-
听起来你可能需要某种排队系统。看看Oracle's Advanced Queuing(它比看起来更简单!您创建一个表来保存队列数据,在其上创建一个队列,然后根据需要将消息入队和出队,瞧!)
-
It seems FOR UPDATE + SKIP LOCKED 不会一次锁定所有记录,而是在循环期间依次锁定每一行,因此当您通过游标循环并锁定行时,另一个事务可以锁定一行,将行插入 TST_SAMPLE_STATUS 并在提交时释放锁(在循环之后)。因此,您可以获取并锁定已由另一个事务处理的行。所以尝试一次获取并锁定行到一个表变量中,然后在循环中遍历它。
-
@valex,在您提供的 AskTOM 帖子中,有一个短语“将在您打开光标时锁定所有行”。肯定是这样,因为我已经测试过这个案例,发现了几个博客和文章,支持这一点。不过,谢谢,我会尝试一次获取所有行。
-
@Boneist,可能整个 AQ 环境对于我的小目的来说非常庞大。
-
@diziaq 整句:“open cursor for select ... for update 将在您打开游标的那一刻锁定所有行。但是, open cursor for select ... for update skip locked;不锁定 any 行。它们在您 fetch" 时被锁定
标签: sql multithreading oracle locking select-for-update