有人告诉我,以下代码不会帮助我检查双重性,因为在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的这一部分暗示:
事务隐含地以获取TX的任何操作开始 锁:
发布修改数据的语句时
发出SELECT ... FOR UPDATE语句时
使用SET TRANSACTION语句或DBMS_TRANSACTION包显式启动事务时
所以? SELECT是否开始交易?
答案 0 :(得分:4)
后者是真的:https://docs.oracle.com/cloud/latest/db112/SQLRF/statements_10005.htm#SQLRF01705
事务隐含地以获取TX的任何操作开始 锁:
- 发布修改数据的语句时
- 发出SELECT ... FOR UPDATE语句时
- 使用SET TRANSACTION语句或DBMS_TRANSACTION包显式启动事务时
但从主要问题的角度来看,无关紧要 - 查看记录是否已存在于数据库中。即使使用SET TRANSACTION ...
进行明确的交易,您的代码也不会检测到重复的交易!
只需在两个同时进行的会话中手动模拟该过程进行简单测试,您将看到:
CREATE TABLE Publishers(
id int,
name varchar2(100)
);
假设在会话#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.
假设在会话#2中,相同的过程从8:00:00.0020开始,就在会话1中插入之后,但仍然在会话#1提交之前:
SQL> Set transaction name 'session 2';
Transaction set.
SQL> select count(*) FROM Publishers where name = 'John';
COUNT(*)
----------
0
事务#2没有看到会话1完成的未经修改的更改,因此会话2假定没有记录John
,因此它也将其插入到表中:
SQL> INSERT INTO Publishers(id,name) VALUES(1,'John');
1 row created.
现在会话1提交:
SQL> Commit;
Commit complete.
并且几毫秒之后session2也会提交:
SQL> Commit;
Commit complete.
最终结果是 - 即使交易已明确启动,也会出现重复记录:
select * from publishers;
ID NAME
---------- ----------------------------------------------------------------------------------------------------
1 John
1 John
==========编辑=================
您可以通过执行语句SET TRANSACTION来避免重复 隔离级别在开始时可串行化。 - @Draex _
许多人认为ISOLATION LEVEL SERIALIZABLE
会神奇地解决问题。 不幸的是,它无济于事。
让我们看一下它如何运作一个简单的例子:
会话#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.
会话#1:
SQL> commit;
Commit complete.
SQL> select * from publishers;
ID NAME
---------- --------
1 John
并返回会话#2
SQL> commit;
Commit complete.
SQL> select * from publishers;
ID NAME
---------- --------
1 John
1 John
正如您所看到的,ISOLATION LEVEL SERIALIZABLE
的魔力无效。