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
我打算创建一个简单的负载测试:
- 一个帐户TABLE
- 更新账户余额的程序
- A Start-Script 使用 Oracle dbms_scheduler 到 运行 并行过程。
每个程序调用覆盖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_Time
和 End_Time
.
UPDATE Accounts
SET Start_Time = Systimestamp
, End_Time = NULL
WHERE Acc_Id = p_Acc_Id;
如果它是 load test
,我们总是可以在实际开始负载测试之前将值更新回 0
,否则需要找到另一种自动更新的方法。
我会等待您的反馈,看看它是否解决了您的问题并且不需要任何进一步的修改
我打算创建一个简单的负载测试:
- 一个帐户TABLE
- 更新账户余额的程序
- A Start-Script 使用 Oracle dbms_scheduler 到 运行 并行过程。
每个程序调用覆盖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_Time
和 End_Time
.
UPDATE Accounts
SET Start_Time = Systimestamp
, End_Time = NULL
WHERE Acc_Id = p_Acc_Id;
如果它是 load test
,我们总是可以在实际开始负载测试之前将值更新回 0
,否则需要找到另一种自动更新的方法。
我会等待您的反馈,看看它是否解决了您的问题并且不需要任何进一步的修改