【问题标题】:Concurrency control Oracle with Delphi7并发控制Oracle与Delphi7
【发布时间】:2017-03-10 01:12:10
【问题描述】:

我们面临以增量方式访问 Oracle 行的问题,表架构是-

myTable(UniqueNum,flag)

UniqueNum 是数字不重复的字段,flag 是默认值为 F 的字段。

我的编程步骤是:

  1. 获取标志为 F 的 UniqueNum。

    select min(UniquaNum) 
      from myTable 
     where flag='F';
    
  2. 执行操作。

一些操作(comport上的通信)

  1. 将标志设置为 T。

    Update myTable 
       set flag='T' 
     where flag='F' 
       and UniqueNum= 'UN'
    

我开发了一个执行这些步骤的 exe (Delphi-7)。当 exe 同时在多台 PC 上运行时会出现此问题。许多 exe 获取相同的 UniqueNum(第 1 步),但其中只有一个 exe 可以执行所有 3 个步骤。

假设 PC1 选择13 并执行步骤 2,此时 PC2 执行步骤 1 然后它也会获取13。那么对于 PC2,我的第 3 步将失败。

我想知道,是否有一些机制可以避免这种类型的获取(行锁或表锁)?

谁能提出更好的解决方案来避免这种并发?如何在 Delphi 或 C# 中实现相同的功能?

【问题讨论】:

  • 这些步骤的代码示例在哪里:1、2、3?究竟是什么问题?别人被锁了? to avoid such type of fetching 究竟是什么类型的获取?您使用的是什么版本的 oracle?
  • 我使用的是 Oracle 9i,步骤也是 - Step1 select min(UniquaNum) from myTable where flag='F';第 2 步 - 一些操作(comport 上的通信) 第 3 步 - 更新 myTable set flag='T' where flag='F' and UniqueNum= 'UN' 假设 PC1 选择 13 并执行步骤 2,此时 PC2 执行 step1那么它也会获取 13。然后我的 step3 将在 PC2 上失败。
  • will be failed for PC2 定义失败。是否被锁定(如果您的第一个会话在第 3 步之后未提交,我假设它会锁定)或显示错误?
  • 您可以创建一个存储过程来返回唯一编号的值。看起来这就是你的逻辑所在。确保在完成获取后立即将标志设置为另一个状态,以避免其他请求返回相同的值。

标签: c# oracle delphi oracle9i


【解决方案1】:

在第 1 步中,您需要执行“select .. for update nowait”。您必须检查查询是否成功并采取相应措施。

【讨论】:

  • 此外,作为一般规则,您只需要选择实际要更改的记录,并尽可能缩短交易时间。
  • transaction as short as possible 在 Oracle 中,事务的长度应该是它需要的长度——只要业务规则需要它,而不是更短也不再。 Oracle 不是 SQL Server,您必须尽快提交以处理锁定问题。
  • ^ SQL Server 也是如此。事务不应短于或长于它必须的。
  • 让我改变说这是一个糟糕的建议(因为它实际上并没有阻止),但它仍然阻止其他 PC 取得任何进展。因为现在他们无法成功完成第 1 步。因此,该过程有效地跨步骤 1-3 序列化;问题刚刚回到客户身上。
【解决方案2】:

由于您没有说失败意味着什么,我只能假设任一行是第二个会话必须等待或第 2 步处理失败,因为您在多个会话中的查询返回相同的最小数量。如果您使用的是 11g 及更高版本,则可以利用 for update skip locked 子句。但是您使用的是 9i,因此您必须效仿这一点。这是一种方法:

由于UniqueNum正如你所说(UniqueNum is a field where number does not repeat是一个数字不重复的字段)包含唯一值,我们不必实际使用min()函数,我们可以order byUniqueNum

这里的函数next_min_number 将替换您的select min() from 查询。它的作用是首先尝试锁定(最小的行uniquenum),如果行已经被锁定,它会尝试锁定下一个。一旦成功,它将返回 UniqueNum 的值。

create or replace function next_min_number 
 return number
 is
   l_res number;
   row_locked exception;
   pragma exception_init(row_locked, -54);
 begin
    for i in (select un
                from t1
               where flag = 'F' /*of course this value can be passed as parameter */
               order by un ) 
    loop
      begin
        -- trying to lock the row
        select un
          into l_res
          from t1
         where un = i.un
         for update nowait;
         /*row of interest is locked, exit the loop*/
         exit;
       exception 
         when row_locked then null;
         when no_data_found then null;         
       end;     
    end loop;
    return l_res;
 end;

注意:取决于您在Flag 列中有多少带有F 的记录 只索引FlagF 的记录是个好主意。

注意 2:如果任一列可以包含“NULL”,则必须相应地处理它。

测试用例:

设置:

create table t1(
  un number,
  flag varchar2(1)
);

insert into t1
  select level
       , 'F'
    from dual
   connect by level <= 3;

 commit;

select *
  from t1

       UN FLAG
---------- ----
         1 F   
         2 F   
         3 F 

会话#1:

 set serveroutput on;
 var min_num number;
 -- your program
 begin
     :min_num := next_min_number;
     dbms_output.put_line('Current min number: ' || to_char(:min_num));
     dbms_output.put_line('Updating...');
    end;
  /
Current min number: 1                                                           
Updating...                                                                     
PL/SQL procedure successfully completed.

会话 #2

set serveroutput on;
var min_num number;
 -- your program
begin
    :min_num := next_min_number;
    dbms_output.put_line('Current min number: ' || to_char(:min_num));
    dbms_output.put_line('Updating...');
  end;
 /
Current min number: 2                                                           
Updating...                                                                     
PL/SQL procedure successfully completed

我们可以看到同时运行的两个会话得到不同的数字。

然后每个会话都会发布更新。

会话#1

 update t1
    set flag = 'T'
  where flag = 'F'
    and un = :min_num;

1 row updated.

commit;

会话#2

 update t1
    set flag = 'T'
  where flag = 'F'
    and un = :min_num;

1 row updated.

commit;

结果:

select *
  from t1;

        UN FLAG
---------- ----
         1 T   
         2 T   
         3 F   

3 rows selected.

【讨论】:

    【解决方案3】:

    Oracle 的方式是使用序列。

    CREATE SEQUENCE customers_seq
     START WITH     1000
     INCREMENT BY   1
     NOCACHE
     NOCYCLE;
    
    SELECT customers_seq.nextval from DUAL;
    

    这是我所知道的用于超过 1 个客户端的稳定版本的唯一方法。每次您要求时,此 Select 都会给出一个新号码。

    当然你可以把它封装成一个函数

    function GiveNextID():integer;
    

    或任何类似的东西。

    据我所知,您可以将其作为列的默认值放入表定义中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-20
      • 2012-01-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多