相同数据时的Oracle行锁更新

Posted

技术标签:

【中文标题】相同数据时的Oracle行锁更新【英文标题】:Orace Row Lock For Update When Same Data 【发布时间】:2019-08-12 13:18:43 【问题描述】:

我的表中有 5 个优惠券代码,这些优惠券代码是相同的。如果 10 位客户同时应用优惠券代码 [FIRST5],那么我需要分别为 5 位客户更新优惠券为“LOCKED”和 CUST_ID。对于这种情况,我尝试在 SQL 下锁定行并在客户申请优惠券时获取 P_KEY 以更新状态和客户 ID。但我无法为各个客户更新最新的 P_KEY。请指教正确的做法。

SELECT P_KEY FROM
(SELECT P_KEY FROM COUPON_DETAILS WHERE COUPON_CODE = 'FIRST5' 
AND (STATUS  = 'UNLOCK' OR STATUS IS NULL))
WHERE ROWNUM = 1 FOR UPDATE;

P_KEY   COUPON_CODE     STATUS  CUST_ID
1       FIRST5          UNLOCK
2       FIRST5          UNLOCK
3       FIRST5          UNLOCK
4       FIRST5          UNLOCK
5       FIRST5          UNLOCK

【问题讨论】:

你所做的似乎是正确的,我建议你可以将“select...for update and update COUPON_DETAILS in a stored proc/package”包装起来,并让调用程序调用这个存储过程 【参考方案1】:

如果有 10 位顾客同时申请优惠码 [FIRST5],则 我需要将优惠券分别更新为“LOCKED”和 CUST_ID,仅适用于 5 位客户。

我不知道有什么好的纯 SQL 方法可以做到这一点,因为FOR UPDATE 子句不会影响查询的结果集。它只影响获取行的方式

所以,你可能想试试这个:

SELECT p_key 
FROM   coupon_details
WHERE  coupon_code = 'FIRST5'
AND    (status = 'UNLOCK' OR status IS NULL)
AND    rownum = 1
FOR UPDATE SKIP LOCKED;

有理由认为这将导致 Oracle 读取所有匹配的 coupon_details 行,跳过任何被锁定的行,然后在第一行之后停止。但这只有在 for update 子句之后应用 rownum=1 条件时才有效。

不幸的是,它的工作方式是首先应用rownum=1 条件,因为FOR UPDATE 仅在获取期间发生。因此,最终发生的是每个会话只查看第一行。如果未锁定,则返回 p_key。但如果第一行被锁定,它不会返回任何数据。 (或者,对于您发布的查询,其中不包括 SKIP LOCKED,第一个会话之后的会话将等待。)

您真正需要做的是选择所有行,然后获取它们(跳过锁定的行),然后在第一行之后停止。

为此,您需要 PL/SQL。这是一个例子:

DECLARE
  c SYS_REFCURSOR;
  l_key coupon_details.p_key%TYPE;
BEGIN
    -- Open a cursor on all the coupon details that are available to lock
    OPEN c FOR
        SELECT p_key 
        FROM   coupon_details
        WHERE  coupon_code = 'FIRST5'
        AND    (status = 'UNLOCK' OR status IS NULL)
        FOR UPDATE SKIP LOCKED;
    -- Fetch the first one.  The (FOR UPDATE SKIP LOCKED) will ensure that
    -- the one we fetch is not locked by another user and, after fetching,
    -- will be locked by the current session.
    FETCH c INTO l_key;
    -- Do what you need with the locked row.  In this example, we'll
    -- just print some debug messages.
    IF l_key IS NULL THEN
      DBMS_OUTPUT.PUT_LINE('No free locks!');
    ELSE
      DBMS_OUTPUT.PUT_LINE('Locked key ' || l_key);
    END IF;
    -- Close the cursor
    CLOSE c;
END;

...请务必在提交之前UPDATE coupon_details SET status = 'LOCKED' WHERE p_key = l_key

【讨论】:

以上是关于相同数据时的Oracle行锁更新的主要内容,如果未能解决你的问题,请参考以下文章

Oracle中的锁

InnoDB的行锁模式及加锁方法

InnoDB的行锁模式及加锁方法

select for update行锁

关于MySQL中的表锁和行锁

oracle行锁select for update