2个并发事务中的行数
rowcount in 2 concurrent transactions
我有以下代码更新客户,如果它不存在,它 inserts/creates 它。
UPDATE CUSTOMER SET ...
IF SQL%rowcount = 0 THEN
INSERT INTO customer ..
问题是此代码在事务中。在生产中,我随机收到以下错误:
ORA-00001: unique constraint (..) violated at the line with the INSERT INTO customer .. (see above)
我的问题是 Oracle 事务是如何工作的?
我同时有 2 个事务(很少见,但可能会发生)。假设事务 1 已通过行数 = 0 的 IF,但尚未提交。假设事务 2 已通过行数 = 0 的 IF(因为事务 1 尚未插入任何内容)。然后事务 1 提交。然后事务 2 提交,它将 "keep" rowcount = 0 或者它将再次检查 IF SQL%rowcount = 0 THEN 以考虑事务 1 已提交的内容?
我描述的并发问题是我能想到的导致生产中出现上述随机错误的唯一原因。
设置特定的事务隔离级别可能会有所帮助?
是的,这正是正在发生的事情。时机必须恰到好处,这就是为什么它是间歇性的。你可以打开两个sqlplus会话来验证这一点。
- 会话 A:更新,没有行更改,因此
SQL%ROWCOUNT
= 0。
- 会话 B:更新,没有行更改,因此
SQL%ROWCOUNT
= 0。
- 会话 A:通过
IF
条件,插入也是如此。
- 会话 B:通过
IF
条件,插入也是如此。
- 会话 A:提交。
- 会话 B:提交。糟糕,违反了约束,因为现在我可以看到会话 A 的更改。
更改隔离级别对您没有帮助。 Oracle 没有任何隔离级别可以让您看到来自另一个会话的 uncommitted 更改(这是一件好事)。
首先要做的是将 UPDATE
和 INSERT
更改为 MERGE
语句。这样,您只有一个语句会成功或失败。就我而言,用条件分隔插入和更新是一种反模式。像这样:
MERGE INTO customer
USING ( SELECT customer_name FROM wherever ) source
ON ( source.customer_name = customer.customer_name )
WHEN NOT MATCHED THEN INSERT VALUES ( source.customer_name )
WHEN MATCHED THEN UPDATE SET ( customer_name = source.customer_name );
MERGE
的缺点是它没有 RETURNING INTO
子句,因此如果您需要它,则必须使用 select/insert.
其次,要阻止两个会话同时插入,你需要做:
LOCK TABLE customer IN SHARE MODE;
我通常不喜欢制作自定义锁,但我不知道有什么其他办法可以解决这个问题。这将阻止其他会话修改 table 直到第一个会话提交,尽管他们可以查询它。这意味着对 table 的访问是序列化的,因此如果您有很多会话试图更新此 table,那可能无法接受 table.
我有以下代码更新客户,如果它不存在,它 inserts/creates 它。
UPDATE CUSTOMER SET ...
IF SQL%rowcount = 0 THEN
INSERT INTO customer ..
问题是此代码在事务中。在生产中,我随机收到以下错误:
ORA-00001: unique constraint (..) violated at the line with the INSERT INTO customer .. (see above)
我的问题是 Oracle 事务是如何工作的? 我同时有 2 个事务(很少见,但可能会发生)。假设事务 1 已通过行数 = 0 的 IF,但尚未提交。假设事务 2 已通过行数 = 0 的 IF(因为事务 1 尚未插入任何内容)。然后事务 1 提交。然后事务 2 提交,它将 "keep" rowcount = 0 或者它将再次检查 IF SQL%rowcount = 0 THEN 以考虑事务 1 已提交的内容?
我描述的并发问题是我能想到的导致生产中出现上述随机错误的唯一原因。
设置特定的事务隔离级别可能会有所帮助?
是的,这正是正在发生的事情。时机必须恰到好处,这就是为什么它是间歇性的。你可以打开两个sqlplus会话来验证这一点。
- 会话 A:更新,没有行更改,因此
SQL%ROWCOUNT
= 0。 - 会话 B:更新,没有行更改,因此
SQL%ROWCOUNT
= 0。 - 会话 A:通过
IF
条件,插入也是如此。 - 会话 B:通过
IF
条件,插入也是如此。 - 会话 A:提交。
- 会话 B:提交。糟糕,违反了约束,因为现在我可以看到会话 A 的更改。
更改隔离级别对您没有帮助。 Oracle 没有任何隔离级别可以让您看到来自另一个会话的 uncommitted 更改(这是一件好事)。
首先要做的是将 UPDATE
和 INSERT
更改为 MERGE
语句。这样,您只有一个语句会成功或失败。就我而言,用条件分隔插入和更新是一种反模式。像这样:
MERGE INTO customer
USING ( SELECT customer_name FROM wherever ) source
ON ( source.customer_name = customer.customer_name )
WHEN NOT MATCHED THEN INSERT VALUES ( source.customer_name )
WHEN MATCHED THEN UPDATE SET ( customer_name = source.customer_name );
MERGE
的缺点是它没有 RETURNING INTO
子句,因此如果您需要它,则必须使用 select/insert.
其次,要阻止两个会话同时插入,你需要做:
LOCK TABLE customer IN SHARE MODE;
我通常不喜欢制作自定义锁,但我不知道有什么其他办法可以解决这个问题。这将阻止其他会话修改 table 直到第一个会话提交,尽管他们可以查询它。这意味着对 table 的访问是序列化的,因此如果您有很多会话试图更新此 table,那可能无法接受 table.