dbms_lock案例一则

本文深入探讨了Oracle的锁管理服务及其在应用程序中的应用,详细介绍了如何通过DBMS_LOCK包来获取不同类型的锁(如NL、SS、SX、S、SSX、X),并解释了这些锁在分布式事务中的使用与释放。文中还提供了实例代码,展示了如何使用DBMS_LOCK包中的函数来获取唯一锁ID和执行锁操作。最后,通过一个具体的例子,演示了如何利用锁机制确保数据的一致性和避免并发处理中的冲突。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天来深入学习下Oracle的锁:

About User Locks

You can use Oracle Lock Management services  for your applications by making calls

to the  DBMS_LOCK package. It is possible to request a lock of a specific mode, give it a

unique name recognizable in another procedure in the same or another instance,

change the lock mode, and release it. Because a reserved user lock is the same as an

Oracle Database lock, it has all the features of a database lock, such as deadlock

detection. Be certain that any user locks us ed in distributed transactions are released

upon COMMIT , or an undetected deadlock can occur.

在使用锁的过程中不免会涉及到 dbms_lock 这个包,我们可以通过 dbms_lock .request来获得某种类型的锁(NL,SS,SX,S,SSX,X),用(commit,release)等方式来释放该锁,可以通过dbms_lock.allocate_unique来获得唯一的lockid

在oracle的官方文档中对dbms_lock中的几个常数有如下说明,我们可以根据需要来获取不同的锁类型:

例如:

l_lock_flag   := dbms_lock.request(lockhandle => l_lock_handle,

                                                  lockmode => dbms_lock.x_mode,

                                                  timeout => 6000,

                                                  release_on_commit => TRUE);

在这里获取一个eXclusive(排他dbms_lock.x_mode)锁,6000超时,提交时释放,并且lockhandle为l_lock_handle

包中各类型锁的常数值如下:

 nl_mode  constant integer := 1;

 ss_mode  constant integer := 2;-- Also called 'Intended Share'

 sx_mode  constant integer := 3;-- Also called 'Intended Exclusive'

 s_mode   constant integer := 4;

 ssx_mode constant integer := 5;

 x_mode   constant integer := 6;

当一个进程持有某个类型的锁,而另一个类型企图获得相同对象的某种锁的时候,他们之间的兼容关系如下:


不明白各个类型表示的意思可以看看下面这段话:

 --  A sub-share lock can be used on an aggregate object to indicate that

 --  share locks are being aquired on sub-parts of the object.  Similarly, a

 --  sub-exclusive lock can be used on an aggregate object to indicate

 --  that exclusive locks are being aquired on sub-parts of the object.  A

 --  share-sub-exclusive lock indicates that the entire aggregate object

 --  has a share lock, but some of the sub-parts may additionally have

 --  exclusive locks.

下面来做个例子,某个表中有如下15条数据:



很多时候我们可能会重复处理procedure_result表中行,导致出现如下情况:

这时候就需要对表中的行进行单独锁定:

下面是使用dbms_lock的一个例子:

CREATE OR REPLACE PACKAGE BODY pkg_lock_util IS

  --  Given a name, generate a unique lockid for this lock.  This procedure
  --    always performs a 'commit'.
  --  Input parameters:
  --    lockname
  --      name of lock to generate unique lockid for.  If this name already
  --      has been assigned a lockid, then return a handle to that lockid.
  --      Otherwise generate a new lockid and return a handle to it.
  --      WARNING: Do not use locknames beginning with 'ORA$'; these names
  --      are reserved for products supplied by Oracle Corporation.  The
  --      name can be up to 128 bytes, and is case-sensitive.
  --    expiration_secs
  --      number of seconds after an 'allocate_unique' is last performed on
  --      this lock name that this lock is subject to cleanup (i.e.,
  --      deleting from the dbms_lock_allocated table).  Defaults to 10
  --      days.
  --  Output parameters:
  --    lockhandle
  --      The actual lockid is not returned, rather a handle to it is
  --      returned.  Use this handle in subsequent calls to request,
  --      convert and release. Up to 128 bytes are returned.  A handle
  --      is used to reduce the chance that a programming error can
  --      accidentally create an incorrect but valid lockid.  This will
  --      provide better isolation between different applications that are
  --      using this package.
  --
  --      All sessions using a lockhandle returned by a call to
  --      allocate_unique using the same name will be referring to the same
  --      lock.  Different sessions may have different lockhandles for the
  --      same lock, so lockhandles should not be passed from one session
  --      to another.
  --
  --      The lockid's generated by allocate_unique are between 1073741824
  --      and 1999999999, inclusive.
  --
  --      This routine will always do a commit.
  --
  --  Errors raised:
  --    -20000, ORU-10003: Unable to find or insert lock <lockname>
  --        into catalog dbms_lock_allocated.
  FUNCTION allocate_handle(in_lock_type IN NUMBER,
                           in_lock_name IN VARCHAR2) RETURN VARCHAR2 IS
    l_lock_handle VARCHAR2(32767);
  BEGIN
    dbms_lock.allocate_unique(in_lock_type || ':' || in_lock_name,
                              l_lock_handle);
    RETURN(l_lock_handle);
  END allocate_handle;

  /*
    nl_mode  constant integer := 1;
    ss_mode  constant integer := 2; -- Also called 'Intended Share'
    sx_mode  constant integer := 3; -- Also called 'Intended Exclusive'
    s_mode   constant integer := 4;
    ssx_mode constant integer := 5;
    x_mode   constant integer := 6;
    
    dbms_lock.request
    --  Return value:
    --    0 - success
    --    1 - timeout
    --    2 - deadlock
    --    3 - parameter error
    --    4 - already own lock specified by 'id' or 'lockhandle'
    --    5 - illegal lockhandle
  */

  FUNCTION get_lock(in_lock_type IN NUMBER,
                    in_lock_name IN VARCHAR2) RETURN INTEGER IS
    l_lock_handle VARCHAR2(32767);
    l_lock_flag   INTEGER;
  BEGIN
    l_lock_handle := allocate_handle(in_lock_type, in_lock_name);
    l_lock_flag   := dbms_lock.request(lockhandle => l_lock_handle,
                                       lockmode => dbms_lock.x_mode,
                                       timeout => 6000,
                                       release_on_commit => TRUE);
    RETURN(l_lock_flag);
  END get_lock;

  PROCEDURE p_lock_util_test IS
    v_lock_flag INTEGER;
    v_result     VARCHAR2(10);
  BEGIN
    v_lock_flag := dbms_lock.request(1, dbms_lock.s_mode,
                                     release_on_commit => TRUE);
    COMMIT;
    FOR c1 IN (SELECT * FROM procedure_result a WHERE a.result = 1) LOOP
      v_lock_flag := get_lock(in_lock_type => c1.procedure_id,
                              in_lock_name => c1.procedure_name);
      IF v_lock_flag = 0 THEN
        SELECT a.result
        INTO   v_result
        FROM   procedure_result a
        WHERE  a.procedure_id = c1.procedure_id;
        IF v_result = 1 THEN
          UPDATE procedure_result a SET a.comments = a.comments || 'start...', a.result = 2 WHERE a.procedure_id = c1.procedure_id;
          COMMIT;
          dbms_output.put_line(c1.procedure_id);
        END IF;
      END IF;
    END LOOP;
  END p_lock_util_test;
END pkg_lock_util;


先请求个排他锁,不要提交:

DECLARE

 v_lock_flag INTEGER;

BEGIN

 v_lock_flag := dbms_lock.request(1, dbms_lock.x_mode,

                                    release_on_commit => TRUE);

END;

/

然后开两个会话执行上面的过程p_lock_util_test ,因为过程一开始需要申请共享锁,但是对应的这个id已经被请求排他锁锁阻塞。

这时候提交最早之前的请求,这样两个会话执行的过程p_lock_util_test 就可以同时开始执行测试了。

再看看执行之后的结果如下:

这样就能够保证每一行都只被更新一次了。

### DBMS_LOCK.SLEEP 函数介绍 在 Oracle PL/SQL 中,`DBMS_LOCK.SLEEP` 是一个用于使当前会话暂停执行指定秒数的过程。这可以用来模拟延迟或其他需要等待的情况。 #### 基本语法 ```sql DBMS_LOCK.SLEEP(seconds IN NUMBER); ``` 参数 `seconds` 表示要休眠的时间长度(以秒为单位)。此过程不会返回任何值[^3]。 #### 示例代码 下面是一个简单的例子,展示如何使用 `DBMS_LOCK.SLEEP` 来创建两秒钟的延时: ```plsql BEGIN DBMS_OUTPUT.PUT_LINE('Start sleeping...'); DBMS_LOCK.SLEEP(2); -- Sleep for 2 seconds DBMS_OUTPUT.PUT_LINE('Woke up after 2 seconds'); END; / ``` 需要注意的是,在某些环境中可能无法直接看到 `DBMS_OUTPUT.PUT_LINE` 的输出,除非启用了服务器输出功能或将其重定向到文件中。 对于更复杂的场景,比如在一个存储过程中引入随机间隔来模仿不规则的任务处理时间,也可以利用该函数实现: ```plsql DECLARE v_random_interval NUMBER := ROUND(DBMS_RANDOM.VALUE * 5, 0); -- Generate a random number between 0 and 5 (inclusive) BEGIN DBMS_OUTPUT.PUT_LINE('Simulating task with ' || TO_CHAR(v_random_interval) || ' second delay.'); DBMS_LOCK.SLEEP(v_random_interval); DBMS_OUTPUT.PUT_LINE('Task completed.'); END; / ``` #### 注意事项 - 如果尝试从不允许调用 `DBMS_LOCK` 包的地方访问这个包,则可能会遇到权限错误。确保有足够的权限授予给相应的角色或用户[^4]。 - 当前版本的 Oracle 数据库支持多种方式来进行短暂挂起操作;除了 `DBMS_LOCK.SLEEP` 外还有其他替代方案如 `UTL_THREAD.sleep()` 或者通过操作系统命令行工具完成类似的功能[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值