Sql 强制Oracle返回前N行并锁定跳过

Sql 强制Oracle返回前N行并锁定跳过,sql,sql-server,oracle,queue,Sql,Sql Server,Oracle,Queue,关于如何实现类似于表锁的队列的特定行,如何选择一定数量的行,以及如何跳过Oracle和SQL Server中当前锁定的行,有很多讨论 假设至少有N行符合条件,如何保证检索到一定数量的N行 据我所见,Oracle在决定跳过哪些行之前应用WHERE谓词。这意味着,如果我想从一个表中提取一行,并且两个线程同时执行相同的SQL,那么一个线程将接收该行,而另一个线程将接收一个空结果集,即使有更多符合条件的行 这与SQLServer处理UPDLOCK、ROWLOCK和READPAST锁提示的方式相反。在SQ

关于如何实现类似于表锁的队列的特定行,如何选择一定数量的行,以及如何跳过Oracle和SQL Server中当前锁定的行,有很多讨论

假设至少有N行符合条件,如何保证检索到一定数量的N行


据我所见,Oracle在决定跳过哪些行之前应用WHERE谓词。这意味着,如果我想从一个表中提取一行,并且两个线程同时执行相同的SQL,那么一个线程将接收该行,而另一个线程将接收一个空结果集,即使有更多符合条件的行

这与SQLServer处理UPDLOCK、ROWLOCK和READPAST锁提示的方式相反。在SQL Server中,在成功获得锁后,TOP神奇地限制了记录的数量

请注意,两篇有趣的文章和

神谕

在两个单独的会话中,执行:

SELECT qt.ID
FROM QueueTest qt
WHERE qt.ID IN (
    SELECT ID
    FROM
        (SELECT ID FROM QueueTest WHERE Locked IS NULL ORDER BY Priority)
    WHERE ROWNUM = 1)
FOR UPDATE SKIP LOCKED
BEGIN TRANSACTION
SELECT TOP 1 qt.ID
FROM QueueTest qt
WITH (UPDLOCK, ROWLOCK, READPAST)
WHERE Locked IS NULL
ORDER BY Priority;
请注意,第一个会话返回一行,第二个会话不返回一行:

第1次会议

ID ---- 4 ID ---- 4 在两个单独的会话中,执行:

SELECT qt.ID
FROM QueueTest qt
WHERE qt.ID IN (
    SELECT ID
    FROM
        (SELECT ID FROM QueueTest WHERE Locked IS NULL ORDER BY Priority)
    WHERE ROWNUM = 1)
FOR UPDATE SKIP LOCKED
BEGIN TRANSACTION
SELECT TOP 1 qt.ID
FROM QueueTest qt
WITH (UPDLOCK, ROWLOCK, READPAST)
WHERE Locked IS NULL
ORDER BY Priority;
请注意,两个会话都返回不同的行

第1次会议

ID ---- 4 ID ---- 4 第2次会议

ID ---- ID ---- 3
如何在Oracle中获得类似的行为?

据我所见,Oracle在决定跳过哪些行之前应用WHERE谓词

是的。这是唯一可能的办法。在确定结果集之前,不能跳过结果集中的一行

答案就是不限制SELECT语句返回的行数。您仍然可以使用第一行提示来指示优化器您不会获取完整的数据集

调用SELECT的软件应仅选择前n行。在PL/SQL中,应该是

DECLARE
  CURSOR c_1 IS  
    SELECT /*+FIRST_ROWS_1*/ qt.ID
    FROM QueueTest qt
    WHERE Locked IS NULL
    ORDER BY PRIORITY
    FOR UPDATE SKIP LOCKED;
BEGIN
  OPEN c_1;
  FETCH c_1 into ....
  IF c_1%FOUND THEN
     ...
  END IF;
  CLOSE c_1;
END;

在第一个会话中,执行以下操作时:

SELECT qt.ID
FROM QueueTest qt
WHERE qt.ID IN (
    SELECT ID
    FROM
        (SELECT ID FROM QueueTest WHERE Locked IS NULL ORDER BY Priority)
    WHERE ROWNUM = 1)
FOR UPDATE SKIP LOCKED
您的内部选择尝试仅获取id=4并锁定它。这是成功的,因为这一行尚未锁定

在第二个会话中,您的内部select仍然尝试仅获取id=4并锁定它。这是不成功的,因为第一个会话仍锁定该单行

现在,如果您在第一个会话中更新了锁定字段,那么下一个要运行该选择的会话将获取id=3

基本上,在您的示例中,您依赖于未设置的标志。要使用锁定标志,您可能想执行以下操作:

根据某些条件选择所需的ID。 立即更新这些ID的locked flag=1如果资源繁忙,另一个会话抢先让您执行此步骤获取1个或多个ID,请再次转到1 在这些证件上做什么都行 将锁定标志更新回null 然后,您可以使用select for update skip locked语句,因为您的locked标志正在维护中

就我个人而言,我不喜欢您的解决方案可能出于任何原因需要对标记进行的所有更新,因此我可能会尝试在每个会话中根据任何标准选择要更新的ID:

从队列测试中选择*,其中。。。对于更新跳过锁定

例如,在现实中,我的标准不会基于ID列表,但queuetest表过于简单:

sess 1:从queuetest中选择*,其中 更新跳过锁定的id在4,3中

sess 2:从queuetest中选择*,其中 4,3,2中的id用于更新跳过锁定

这里sess1将锁定4,3,而sess2仅锁定2


据我所知,你不能在select for update语句中使用top-n或group\u by/order\u by等,你会得到一个ORA-02014。

Gary Meyers发布的解决方案是我所能想到的全部,除了使用AQ之外,AQ为你做了所有这些,还有更多

如果您真的想避免PLSQL,那么应该能够将PLSQL转换为Java JDBC调用。您所需要做的就是准备相同的SQL语句,执行它,然后继续对其执行单行抓取或N行抓取

位于的Oracle文档提供了一些如何在语句级别执行此操作的线索:

要设置查询的获取大小,请在执行查询之前对语句对象调用setFetchSize。如果将fetch size设置为N,则每次访问数据库时都会获取N行

因此,您可以用Java编写类似于伪代码的代码:

stmt = Prepare('SELECT /*+FIRST_ROWS_1*/ qt.ID
FROM QueueTest qt
WHERE Locked IS NULL
ORDER BY PRIORITY
FOR UPDATE SKIP LOCKED');

stmt.setFetchSize(10);
stmt.execute();

batch := stmt.fetch();
foreach row in batch {
  -- process row
}
commit (to free the locks from the update)
stmt.close;
使现代化 根据下面的评论,有人建议使用ROWNUM来限制收到的结果,但在这种情况下,这不起作用。考虑这个例子:

create table lock_test (c1 integer);

begin
  for i in 1..10 loop
    insert into lock_test values (11 - i);
  end loop;
  commit;
end;
/
现在我们有一张10行的桌子。请注意,我已经小心地按相反顺序插入了行,包含10的行是第一行,然后是9,以此类推

假设您想要前5行,按升序排列-即1到5。您的第一次尝试是:

select *
from lock_test
where rownum <= 5
order by c1 asc;
这显然是错误的,几乎每个人都会犯这样的错误!查看查询的解释计划:

尝试将for update移动到视图中会出现语法错误:

select * from
(
  select *
  from lock_test
  order by c1 asc
  for update skip locked
)
where rownum <= 5;
如果在会话1中运行该块,它将打印出“1”,因为它在第一行中得到了一个锁定。然后在会话2中再次运行它,它将在跳过第1行时打印“2” 得到了下一个免费的


此示例在PLSQL中,但使用Java中的setFetchSize,您应该能够获得完全相同的行为。

我的解决方案是编写如下存储过程:

CREATE OR REPLACE FUNCTION selectQueue 
RETURN SYS_REFCURSOR
AS
  st_cursor SYS_REFCURSOR;
  rt_cursor SYS_REFCURSOR;
  i number(19, 0);

BEGIN

  open st_cursor for
  select id
  from my_queue_table
  for update skip locked;

  fetch st_cursor into i;
  close st_cursor;

  open rt_cursor for select i as id from dual;
  return  rt_cursor;

 END;
这是一个简单的示例-返回顶部第一个非阻塞行。要检索前N行,请将单个fetch替换为局部变量i,并将循环fetch替换为临时表


PS:返回光标-用于hibernate友谊

我遇到了这个问题,我们花了很多时间来解决它。有些用于update for update skip locked,在oracle 12c中,一种新方法是仅使用fetch first n行。但我们使用oracle 11g

最后,我们尝试了这种方法,发现效果很好

CURSOR c_1 IS  
   SELECT *
     FROM QueueTest qt
     WHERE Locked IS NULL
     ORDER BY PRIORITY;
   myRow c_1%rowtype;
   i number(5):=0;
   returnNum := 10;
BEGIN
  OPEN c_1;
  loop 
    FETCH c_1 into myRow 
    exit when c_1%notFOUND 
    exit when i>=returnNum;
    update QueueTest set Locked='myLock' where id=myrow.id and locked is null;
    i := i + sql%rowcount;
  END
  CLOSE c_1;
  commit;
END;

我把它写在记事本里,所以可能出了什么问题,你们可以把它修改成一个过程或者别的

首先感谢前两个答案..从他们身上学到了很多。我测试了以下代码,在运行PracticeDondel.java main方法后,我发现这两个类每次打印不同的行。请让我知道,如果在任何情况下,这个代码可能会失败

PracticeDondel.java:

    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs =null;
    String val="";
    int count =0;

        conn = getOracleConnection();
        conn.setAutoCommit(false);
        ps = prepareStatement(conn,"SELECT /*+FIRST_ROWS_3*/ t.* from 
        REPROCESS_QUEUE t FOR UPDATE SKIP LOCKED");
        ps.setFetchSize(3);
        boolean rss = ps.execute();
        rs = ps.getResultSet();
        new Practisethread().start();
        while(count<3 && rs.next())
        {
            val = rs.getString(1);
            System.out.println(val);
            count++;
            Thread.sleep(10000);
        }
       conn.commit();
            System.out.println("end of main program");
Practisethread.java:正在运行:

            conn = getOracleConnection();
            conn.setAutoCommit(false);
            ps = prepareStatement(conn,"SELECT /*+FIRST_ROWS_3*/ t.* from REPROCESS_QUEUE t FOR UPDATE SKIP LOCKED");
            ps.setFetchSize(3);
            boolean rss = ps.execute();
            rs = ps.getResultSet();
            while(count<3 && rs.next())
            {
                val = rs.getString(1);
                System.out.println("******thread******");
                System.out.println(val);
                count++;
                Thread.sleep(5000);
            }
            conn.commit();
            System.out.println("end of thread program");

FIRST_ROWS\N和FIRST_ROWSN不限制返回的结果,而是优化查询引擎以更快地返回这些行。这会导致所有行被锁定,同时执行时仍会导致类似的行为。第一个线程将锁定所有行,第二个线程将跳过锁定,在第二次查看时看不到任何内容。因为您使用的是游标,所以在根据我在此处找到的内容从游标中提取之前,该行不会被锁定。我试图在没有PL/SQL的情况下解决这个问题,我想从JDBC执行一个简单的SQL批处理。使用游标跳过锁定行的可能性可能是一个选项,特别是当游标作为REF游标从存储过程返回时(如果可能的话)。但是像OP一样,我更希望在一个SELECT语句中找到一个类似SQL Server的解决方案。我将悬赏给一个比Gary Myers的游标更简单的人,因为我也想省略游标,与OPQ一样,并发队列的要点是允许多个客户端处理符合某些条件的记录。处理比仅仅更新行中的数据更复杂。典型的用法是多个客户端尝试使用指定的完全相同的条件访问行。在您的示例中,第一个会话可能会锁定所有记录,使会话2处于饥饿状态。如果两个客户端希望访问并锁定由完全相同的条件指定的完全相同的行,则一个客户端将赢得锁定这些行,另一个客户端将失去锁定,从而导致资源繁忙。在我的示例中,会话1尝试锁定2行并成功锁定2行,会话2尝试锁定3行并仅成功锁定1行。您不能让多个会话锁定同一行。@特拉维斯:我认为您在Oracle的OP示例中缺少的是,您从未设置锁定字段,而是尝试在子选择中使用它。对于这两个会话,它与。。。我的身份证在哪里。。。因为SELECT ID FROM SELECT ID FROM QueueTest WHERE Locked是NULL ORDER BY Priority,其中ROWNUM=1将始终返回4,如果您不更新表中的Locked标志。显然,SqlServer允许您在锁定选择中执行top-n,但Oracle不允许使用select for update的组函数。据我所知,您将获得ORA-02014,因此我尝试演示ALT,重点是我希望每个客户端只锁定一行,而不是全部。是的,锁定第一行将获胜,在这种情况下,我希望第二行锁定下一行,请参见SQL server示例。锁定字段在post中不相关,因为我希望在事务中获得行锁定后设置它,这将允许它从稍后的其他查询中排除。我现在要检查的问题是,在任何一行上设置Locked标志之前,在两个客户端同时尝试访问之前会发生什么。Locked字段是相关的,因为它用于您的子选择。如果您想使用它,您需要设置它。请参阅我的操作中的步骤1-4。如果您不想使用它,下一个问题是选择要锁定的ID组的标准是什么。我的回答的第二部分是为每个会话选择一个简单的ID列表,但您可以在实际表中使用日期字段或其他任何内容。据我所知,您不能将top-n或类似的组函数用于此子选择以及更新。那么,您选择锁定哪些ID的标准是什么?您确定这会起作用吗?FIRST_ROWS提示将导致Oracle尝试尽快返回第一行,但我相信不管提示中的数字是多少,它最终还是会锁定所有行。该提示实际上并没有限制检索到的记录数或JDBC中设置的获取大小。我不确定它是否会工作,但我认为它会工作。在这个线索的某个地方,有人说Oracle只锁定t
当它返回时,他划船。如果您的fetch大小是10,那么对fetch的一个调用应该会给您10行,并锁定这10行,而不是更多。我想说的是,您必须编写一些代码并尝试一下。我会这么做,但我的Java技能已经非常生疏了。我没有尝试过setFetchSize10,但使用ROWNUM进行限制。我不认为使用ROWNUM限制结果会给你带来你想要的。我已经更新了上面的答案以包含更多信息-基本上,我认为您需要使用setFetchSize。更新后,我认为这可以归结为原始解决方案,但有更多细节。感谢您提供的额外研究/信息。
set serveroutput on

declare
  v_row lock_test%rowtype;
  cursor c_lock_test
  is
  select c1
  from lock_test
  order by c1
  for update skip locked;
begin
  open c_lock_test;
  fetch c_lock_test into v_row;
  dbms_output.put_line(v_row.c1);
  close c_lock_test;
end;
/    
CREATE OR REPLACE FUNCTION selectQueue 
RETURN SYS_REFCURSOR
AS
  st_cursor SYS_REFCURSOR;
  rt_cursor SYS_REFCURSOR;
  i number(19, 0);

BEGIN

  open st_cursor for
  select id
  from my_queue_table
  for update skip locked;

  fetch st_cursor into i;
  close st_cursor;

  open rt_cursor for select i as id from dual;
  return  rt_cursor;

 END;
CURSOR c_1 IS  
   SELECT *
     FROM QueueTest qt
     WHERE Locked IS NULL
     ORDER BY PRIORITY;
   myRow c_1%rowtype;
   i number(5):=0;
   returnNum := 10;
BEGIN
  OPEN c_1;
  loop 
    FETCH c_1 into myRow 
    exit when c_1%notFOUND 
    exit when i>=returnNum;
    update QueueTest set Locked='myLock' where id=myrow.id and locked is null;
    i := i + sql%rowcount;
  END
  CLOSE c_1;
  commit;
END;
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs =null;
    String val="";
    int count =0;

        conn = getOracleConnection();
        conn.setAutoCommit(false);
        ps = prepareStatement(conn,"SELECT /*+FIRST_ROWS_3*/ t.* from 
        REPROCESS_QUEUE t FOR UPDATE SKIP LOCKED");
        ps.setFetchSize(3);
        boolean rss = ps.execute();
        rs = ps.getResultSet();
        new Practisethread().start();
        while(count<3 && rs.next())
        {
            val = rs.getString(1);
            System.out.println(val);
            count++;
            Thread.sleep(10000);
        }
       conn.commit();
            System.out.println("end of main program");
            conn = getOracleConnection();
            conn.setAutoCommit(false);
            ps = prepareStatement(conn,"SELECT /*+FIRST_ROWS_3*/ t.* from REPROCESS_QUEUE t FOR UPDATE SKIP LOCKED");
            ps.setFetchSize(3);
            boolean rss = ps.execute();
            rs = ps.getResultSet();
            while(count<3 && rs.next())
            {
                val = rs.getString(1);
                System.out.println("******thread******");
                System.out.println(val);
                count++;
                Thread.sleep(5000);
            }
            conn.commit();
            System.out.println("end of thread program");