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

Evk*_*a'S 5 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;
Run Code Online (Sandbox Code Playgroud)

这篇文章声称SELECT启动了一个事务: 为什么仅从数据库视图中进行选择时为什么会得到未结事务?

文档的这一部分另有建议:

事务隐式地开始于获得TX锁定的任何操作:

  • 发出修改数据的语句时

  • 发出SELECT ... FOR UPDATE语句时

  • 使用SET TRANSACTION语句或DBMS_TRANSACTION包显式启动事务时

所以?SELECT是否启动事务?

kro*_*lko 4

后者是正确的:https://docs.oracle.com/cloud/latest/db112/SQLRF/statements_10005.htm#SQLRF01705

\n\n
\n

事务隐式地从任何获得 TX\n 锁的操作开始:

\n\n
    \n
  • 当发出修改数据的语句时
  • \n
  • 当发出 SELECT ... FOR UPDATE 语句时
  • \n
  • 当使用 SET TRANSACTION 语句或 DBMS_TRANSACTION 包显式启动事务时
  • \n
\n
\n\n

但这真的不重要,从主要问题的角度来看——查看数据库中是否已经存在该记录。即使事务是明确启动使用的SET TRANSACTION ...,您的代码也根本不会检测到重复事务!

\n只需手动执行一个简单的测试,模拟两个同时会话中的过程,您就会看到:

\n\n
CREATE TABLE Publishers(\n    id int,\n    name varchar2(100)\n);\n
Run Code Online (Sandbox Code Playgroud)\n\n

假设在会话 #1 中,过程于 8:00:00.0000 开始:

\n\n
SQL> Set transaction name \'session 1\';\n\nTransaction set.\n\nSQL> select count(*) FROM Publishers where name = \'John\';\n\n  COUNT(*)\n----------\n         0\n\nSQL> INSERT INTO Publishers(id,name) VALUES(1,\'John\');\n\n1 row created.\n
Run Code Online (Sandbox Code Playgroud)\n\n

假设在会话 #2 中,相同的过程在 8:00:00.0020 开始,就在会话 1 中进行插入之后,但仍在会话 #1 提交之前:

\n\n
SQL> Set transaction name \'session 2\';\n\nTransaction set.\n\nSQL> select count(*) FROM Publishers where name = \'John\';\n\n  COUNT(*)\n----------\n         0\n
Run Code Online (Sandbox Code Playgroud)\n\n

事务 #2 没有看到会话 1 所做的未提交的更改,因此会话 2 假设没有记录John,因此它也将其插入到表中:

\n\n
SQL>  INSERT INTO Publishers(id,name) VALUES(1,\'John\');\n\n1 row created.\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

现在会话 1 提交:

\n\n
SQL> Commit;\n\nCommit complete.\n
Run Code Online (Sandbox Code Playgroud)\n\n

几毫秒后,session2 也提交了:

\n\n
SQL> Commit;\n\nCommit complete.\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

最终结果是 - 即使事务已显式启动,也会出现重复的记录:

\n\n
select * from publishers;\n        ID NAME                                                                                                \n---------- ----------------------------------------------------------------------------------------------------\n         1 John                                                                                                \n         1 John         \n
Run Code Online (Sandbox Code Playgroud)\n\n

=========编辑=================

\n\n
\n

您可以通过在开头执行语句 SET TRANSACTION\n ISOLATION LEVEL SERIALIZABLE 来避免口是心非。\xe2\x80\x93 @Draex_

\n
\n\n

许多人认为这ISOLATION LEVEL SERIALIZABLE会神奇地解决问题。不幸的是,这无济于事

\n让我们通过一个简单的例子来看看它是如何工作的:

\n Session #1

\n\n
SQL>  SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;\n\nTransaction set.\n\nSQL> select count(*) FROM Publishers where name = \'John\';\n\n  COUNT(*)\n----------\n         0\n\nSQL> INSERT INTO Publishers(id,name) VALUES(1,\'John\');\n\n1 row created.\n
Run Code Online (Sandbox Code Playgroud)\n\n

第 2 节

\n\n
SQL> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;\n\nTransaction set.\n\nSQL>  select count(*) FROM Publishers where name = \'John\';\n\n  COUNT(*)\n----------\n         0\n\nSQL> INSERT INTO Publishers(id,name) VALUES(1,\'John\');\n\n1 row created.\n
Run Code Online (Sandbox Code Playgroud)\n\n

再次会话#1:

\n\n
SQL> commit;\n\nCommit complete.\n\nSQL> select * from publishers;\n\n        ID  NAME\n----------  --------\n         1  John\n
Run Code Online (Sandbox Code Playgroud)\n\n

回到会话 #2

\n\n
SQL> commit;\n\nCommit complete.\n\nSQL> select * from publishers;\n            ID  NAME\n    ----------  --------\n             1  John\n             1  John\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

正如你所看到的,它的魔力在于ISOLATION LEVEL SERIALIZABLE没有发挥作用。

\n