有人告诉我,以下代码不会帮助我检查重复性,因为在 SELECT 和 UPDATE 语句之前结果可能不同。
PROCEDURE AddNew(Pname VARCHAR2, Pcountry VARCHAR2)
AS
already_exists BOOLEAN;
BEGIN
SELECT COUNT(*)>0 INTO already_exists FROM Publishers WHERE name=Pname;
IF already_exists THEN
RAISE_APPLICATION_ERROR(-20014,'Publisher already exists!');
END IF;
INSERT INTO Publishers(id,name,country)
VALUES (NewPublisherId(),Pname,Pcountry);
END;
这篇文章声称 SELECT 启动了一个事务:
Why do I get an open transaction when just selecting from a database View?
documentation的这部分否则建议:
A transaction implicitly begins with any operation that obtains a TX lock:
When a statement that modifies data is issued
When a SELECT ... FOR UPDATE statement is issued
When a transaction is explicitly started with a SET TRANSACTION statement or the DBMS_TRANSACTION package
所以? SELECT 是否启动事务?
最佳答案
后者为真:https://docs.oracle.com/cloud/latest/db112/SQLRF/statements_10005.htm#SQLRF01705
A transaction implicitly begins with any operation that obtains a TX lock:
- When a statement that modifies data is issued
- When a SELECT ... FOR UPDATE statement is issued
- When a transaction is explicitly started with a SET TRANSACTION statement or the DBMS_TRANSACTION package
不过真的没关系 ,从主要问题的角度来看——查看该记录是否已经存在于数据库中。即使交易是明确地开始使用
SET TRANSACTION ...
,您的代码根本没有检测到重复的交易!只需在两个同时进行的 session 中手动模拟该过程进行一个简单的测试,您将看到:
CREATE TABLE Publishers(
id int,
name varchar2(100)
);
假设在 session #1 中,程序从 8:00:00.0000 开始:
SQL> Set transaction name 'session 1';
Transaction set.
SQL> select count(*) FROM Publishers where name = 'John';
COUNT(*)
----------
0
SQL> INSERT INTO Publishers(id,name) VALUES(1,'John');
1 row created.
假设在 session #2 中,相同的过程从 8:00:00.0020 开始,就在 session 1 中进行插入之后,但仍然在 session #1 提交之前:
SQL> Set transaction name 'session 2';
Transaction set.
SQL> select count(*) FROM Publishers where name = 'John';
COUNT(*)
----------
0
事务 #2 看不到 session 1 所做的未提交更改,因此 session 2 假定没有记录
John
,所以它也将它插入到表中:SQL> INSERT INTO Publishers(id,name) VALUES(1,'John');
1 row created.
现在 session 1 提交:
SQL> Commit;
Commit complete.
几毫秒后 session2 也提交了:
SQL> Commit;
Commit complete.
最终的结果是 - 即使事务已明确启动,也会出现重复的记录:
select * from publishers;
ID NAME
---------- ----------------------------------------------------------------------------------------------------
1 John
1 John
========== 编辑 ==================
You can avoid the duplicity by executing statement SET TRANSACTION ISOLATION LEVEL SERIALIZABLE in the beginning. – @Draex_
很多人认为
ISOLATION LEVEL SERIALIZABLE
会神奇地解决问题。 不幸的是,它无济于事 .让我们通过一个简单的例子来看看它是如何工作的:
session #1
SQL> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Transaction set.
SQL> select count(*) FROM Publishers where name = 'John';
COUNT(*)
----------
0
SQL> INSERT INTO Publishers(id,name) VALUES(1,'John');
1 row created.
第 2 节
SQL> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Transaction set.
SQL> select count(*) FROM Publishers where name = 'John';
COUNT(*)
----------
0
SQL> INSERT INTO Publishers(id,name) VALUES(1,'John');
1 row created.
session #1 再次:
SQL> commit;
Commit complete.
SQL> select * from publishers;
ID NAME
---------- --------
1 John
并返回 session #2
SQL> commit;
Commit complete.
SQL> select * from publishers;
ID NAME
---------- --------
1 John
1 John
如您所见,
ISOLATION LEVEL SERIALIZABLE
的魔力不工作。
关于sql - SELECT 是否在 PL/SQL 中启动事务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49455629/