oracle:更新时选择前 10 行

发布于 2024-11-15 04:18:49 字数 586 浏览 3 评论 0原文

我有一个 ITEM 表,其中一列为 CREATED_DATE。在集群环境中,许多服务副本将从该表中选取项目并进行处理。每个服务应该从 ITEM 表中选择最旧的 10 个项目。

我可以在存储过程中使用它来选择前 10 行:

select * from (
    select  item_id, row_number() over (order by CREATED_DATE) rownumber
    FROM item )
where rownumber < 11

由于许多服务应该使用它,所以我使用 select ... for update 将行更新为“处理”。但下面的 FOR UPDATE 语句对于上述 select 语句失败,并出现错误“ORA-02014:无法从具有 DISTINCT、GROUP BY 等的视图中选择 FOR UPDATE”。

OPEN items_cursor FOR
**select Statement**
FOR UPDATE;

请帮我提供解决方案。

I have an ITEM table with one of the column as CREATED_DATE. In a clustered enviroment, many copies of services will pick items from this table and process it. Each service should pick the oldest 10 items from the ITEM table.

I am able to select top 10 rows using this in a Stored Procedure:

select * from (
    select  item_id, row_number() over (order by CREATED_DATE) rownumber
    FROM item )
where rownumber < 11

Since many service should use this, I am using select ... for update to update the rows as "processing". But the below FOR UPDATE statement, fails for the above select statement with error "ORA-02014: cannot select FOR UPDATE from view with DISTINCT, GROUP BY, etc."

OPEN items_cursor FOR
**select Statement**
FOR UPDATE;

Please help me with a solution.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

做个ˇ局外人 2024-11-22 04:18:49

这适合您的情况吗?

SELECT *
  FROM item
 WHERE (item_id,created_date) IN
       (SELECT item_id,created_date
          FROM (SELECT item_id, created_date
                     , ROW_NUMBER() OVER (ORDER BY created_date) rownumber
                  FROM item)
         WHERE rownumber < 11)

Does this work for your situation?

SELECT *
  FROM item
 WHERE (item_id,created_date) IN
       (SELECT item_id,created_date
          FROM (SELECT item_id, created_date
                     , ROW_NUMBER() OVER (ORDER BY created_date) rownumber
                  FROM item)
         WHERE rownumber < 11)
不美如何 2024-11-22 04:18:49

DCookie 的答案没有解决多会话处理(它只是用于 UPDATE 语法修复)。如果您不操作行号范围,则每个服务实例都会选择更新相同的行。如果您在两个会话中执行 that_for_update_select,则第二个会话将等待第一个会话完成事务。并行处理将是一种幻想。

我会考虑将高效的批量处理与更新跳过锁定方法结合起来。我的回答如下:

declare 
  con_limit constant number default 10;
  cursor cItems is
    select i.item_id, i.created_date
    from item i
    order by i.created_date
    for update skip locked;
  type t_cItems is table of cItems%rowtype;
  tItems t_cItems;
begin
  open cItems;
  while true loop
    fetch cItems bulk collect into tItems limit con_limit;
    -- processing tItems
    exit when tItems.count < con_limit;
  end loop;
end;

可能的长交易可能是一个缺点。考虑使用 Oracle Streams 高级队列 (DBMS_AQ) 作为此解决方案的替代方案。

DCookie's answer doesn't solve multisession processing (it's just FOR UPDATE syntax fix). If you won't manipulate rownumber range, every instance of service if going to select for update the same rows. If you execute that_for_update_select in two sessions, the second one is going to wait until first finishes the transaction. Parallel processing will be an illusion.

I would consider efficient bulk processing together with for update skip locked approach. My answer below:

declare 
  con_limit constant number default 10;
  cursor cItems is
    select i.item_id, i.created_date
    from item i
    order by i.created_date
    for update skip locked;
  type t_cItems is table of cItems%rowtype;
  tItems t_cItems;
begin
  open cItems;
  while true loop
    fetch cItems bulk collect into tItems limit con_limit;
    -- processing tItems
    exit when tItems.count < con_limit;
  end loop;
end;

Possible long transaction could be a disadvantage. Consider using Oracle Streams Advanced Queuing (DBMS_AQ) as an alternative to this solution.

莫言歌 2024-11-22 04:18:49

您可以使用跳过锁定和计数器来实现此目的,只要您不一定需要每个会话都获取连续的行。例如:

declare
    l_cursor sys_refcursor;
    l_name all_objects.object_name%type;
    l_found pls_integer := 0;
begin
    open l_cursor for
        select  object_name
        from all_objects
        order by created
        for update skip locked;

    loop
        fetch l_cursor into l_name;
        dbms_output.put_line(l_fetches || ':' || l_name);
        if l_cursor%found then
            l_found := l_found + 1;
            -- dbms_lock.sleep(1);
        end if;
        exit when l_cursor%notfound or l_found = 10;
    end loop;
end;
/

如果您从两个会话同时运行此命令,它们将获得不同的对象(尽管您可能需要在 found 块内启用对 dbms_lock.sleep 的调用,以使它足够慢以至于可见)。

根据 这篇文章,当使用 skip锁定 选定的行在被提取之前不会被锁定,并且在游标打开后被另一个会话锁定的任何行都将被忽略。

You can use skip locked and a counter to achieve this, as long as you don't necessarily need each session to get contiguous rows. For example:

declare
    l_cursor sys_refcursor;
    l_name all_objects.object_name%type;
    l_found pls_integer := 0;
begin
    open l_cursor for
        select  object_name
        from all_objects
        order by created
        for update skip locked;

    loop
        fetch l_cursor into l_name;
        dbms_output.put_line(l_fetches || ':' || l_name);
        if l_cursor%found then
            l_found := l_found + 1;
            -- dbms_lock.sleep(1);
        end if;
        exit when l_cursor%notfound or l_found = 10;
    end loop;
end;
/

If you run this simultaneously from two sessions they'll get different objects (though you may need to enable the call to dbms_lock.sleep inside the found block to make it slow enough to be visible).

According to this post, when using skip locked the selected rows aren't locked until they're fetched, and any rows locked by another session after the cursor is opened are just ignored.

我的奇迹 2024-11-22 04:18:49

每个人都建议使用带有循环的 PL/SQL 代码,但也有一个简单的 SQL 解决方案:

select *
from item 
where ROWID in
(
  select ROWID from item 
  order by CREATED_DATE
  fetch next 10 rows only
)
for update skip locked

Everyone is suggesting PL/SQL code with loops, but there's also a plain SQL solution:

select *
from item 
where ROWID in
(
  select ROWID from item 
  order by CREATED_DATE
  fetch next 10 rows only
)
for update skip locked
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文