SELECT是否在PL / SQL中启动事务

时间:2018-03-23 17:47:34

标签: sql oracle plsql transactions

有人告诉我,以下代码不会帮助我检查双重性,因为在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是否开始交易?

1 个答案:

答案 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的魔力无效。