SELECT FOR UPDATE 似乎不适用于 Oracle 12.1/19.3 的 PL/SQL-driven 测试用例

SELECT FOR UPDATE seems not to work in a PL/SQL-driven Testcase with Oracle 12.1/19.3

我打算创建一个简单的负载测试:

每个程序调用覆盖Account_ID 1-16 并增加账户余额500 次。 所以最后账户余额是 500。对于测试用例,我 运行 每个账户 ID 有 4 个并发后台作业来增加账户余额,我希望每个账户 ID 有 2000 个 但结果随机在 1850 到 1950 的范围内;我使用 SELECT 进行更新以锁定要更新的帐户 ID,但它不起作用。有什么想法吗?

table创作:

DROP TABLE ACCOUNTS;
CREATE TABLE ACCOUNTS ( ACC_ID INTEGER NOT NULL , ACC_BALANCE NUMBER (9,3) DEFAULT 0  NOT NULL , START_TIME TIMESTAMP, END_TIME TIMESTAMP);
ALTER TABLE ACCOUNTS ADD CONSTRAINT ACCOUNTS_PK PRIMARY KEY (ACC_ID);


INSERT INTO ACCOUNTS SELECT ROWNUM , 0 , NULL, NULL FROM DBA_USERS DT WHERE ROWNUM <= 16;
COMMIT WORK;

程序:

CREATE OR REPLACE PROCEDURE Paccount_Test(p_Acc_Id     IN INTEGER
                                         ,p_Incr       IN NUMBER
                                         ,p_Num_Incr   IN INTEGER
                                         ,p_Work_Loops IN INTEGER) IS
    Xx            INTEGER := 0;
    l_Acc_Balance Accounts.Acc_Balance%TYPE;
BEGIN

    UPDATE Accounts SET Acc_Balance = 0.0, Start_Time = Systimestamp, End_Time = NULL WHERE Acc_Id = p_Acc_Id;
    COMMIT WORK;

    FOR Nloops IN 1 .. p_Num_Incr LOOP
        SELECT Acc_Balance INTO l_Acc_Balance FROM Accounts WHERE Acc_Id = p_Acc_Id FOR UPDATE;
    
        -- Amoount of work ...
        FOR Ii IN 1 .. p_Work_Loops LOOP
            Xx := Xx + 1;
        END LOOP;
    
        UPDATE Accounts SET Acc_Balance = Acc_Balance + p_Incr WHERE Acc_Id = p_Acc_Id;
        COMMIT WORK;
    END LOOP;

    UPDATE Accounts SET End_Time = Systimestamp WHERE Acc_Id = p_Acc_Id;
    COMMIT WORK;

END Paccount_Test;

64 个进程的起始脚本,...

BEGIN
    FOR Acc_Id IN 1 .. 64 LOOP
        Dbms_Scheduler.Create_Job(Job_Name   => 'One_Time_Job' || Acc_Id
                                 ,Job_Type   => 'PLSQL_BLOCK'
                                 ,Job_Action => 'begin paccount_test (p_acc_id => ' || (mod(Acc_Id,16)+1) ||
                                                ', p_incr => 1, p_num_incr => 500, p_work_loops => 400000); end;'
                                 ,Start_Date => SYSDATE
                                 ,Enabled    => TRUE
                                 ,Auto_Drop  => TRUE
                                 ,Comments   => 'one-time job');
    END LOOP;
END;
/

我检查了 DBA_SCHEDULER_JOB_LOG,但没有看到任何错误,同样在 V$session 64 后台进程处于活动状态。

SELECT 查询的示例输出:

ACC_ID ACC_BALANCE
1      1932,000
2      1900,000
3      1902,000
4      1883,000
5      1910,000
6      1939,000
7      1920,000
8      1910,000
9      1865,000
10     1920,000
11     1916,000
12     1888,000
13     1896,000
14     1909,000
15     1918,000
16     1935,000

我已经模拟了这个问题,问题是 update statement 在开始和 increment loop 开始之前也会将 acc_balance 更新为 0(它可能会发生对于此时没有锁定的任何记录,因此我们看到的值与预期不同 2000),这导致了这里的问题,乍一看对我来说太错过了。

我们也可以用 for update 制作上面的 update ,我没试过,可以解决问题,但我认为我们不需要在这里更新 acc_balance并将其从 update 子句中删除以仅更新 Start_TimeEnd_Time.

UPDATE Accounts 
   SET Start_Time = Systimestamp
     , End_Time = NULL 
 WHERE Acc_Id = p_Acc_Id;

如果它是 load test,我们总是可以在实际开始负载测试之前将值更新回 0,否则需要找到另一种自动更新的方法。

我会等待您的反馈,看看它是否解决了您的问题并且不需要任何进一步的修改