You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PL/SQL中SELECT是否会启动事务?含重复校验代码疑问

PL/SQL中普通SELECT是否会启动事务?

先直接给明确结论:普通的SELECT语句在PL/SQL中不会启动隐式事务——这完全匹配Oracle官方文档的定义。

官方文档明确的隐式事务触发场景

Oracle的隐式事务只会在以下操作需要获取TX锁时才会启动:

  • 执行修改数据的语句(比如INSERT、UPDATE、DELETE、MERGE这类写操作)
  • 执行SELECT ... FOR UPDATE/SELECT ... FOR SHARE这类会锁定目标行的查询语句
  • 使用SET TRANSACTION语句显式配置事务属性
  • 通过DBMS_TRANSACTION包显式启动事务

普通SELECT只是读取数据,不会申请TX锁,自然也不会触发隐式事务的启动。

回到你提到的存储过程并发问题

你给出的AddNew存储过程之所以会出现“检查重复失效”的问题,核心原因不是SELECT启动了事务,而是竞态条件(Race Condition):

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;

当多个会话同时执行这个存储过程时,会话A刚执行完SELECT(判定无重复),还没来得及执行INSERT;此时会话B也执行了相同的SELECT(同样判定无重复),最终两个会话都会执行INSERT,导致重复数据被插入。

怎么解决这个问题?

最可靠且性能最优的方案是给Publishers表的name字段添加唯一约束:

ALTER TABLE Publishers ADD CONSTRAINT uk_publishers_name UNIQUE (name);

然后修改存储过程,捕获唯一约束冲突的异常:

PROCEDURE AddNew(Pname VARCHAR2, Pcountry VARCHAR2) AS 
BEGIN 
    INSERT INTO Publishers(id,name,country) VALUES (NewPublisherId(),Pname,Pcountry); 
EXCEPTION 
    WHEN DUP_VAL_ON_INDEX THEN 
        RAISE_APPLICATION_ERROR(-20014,'Publisher already exists!'); 
END;

这种方式把重复校验的逻辑交给数据库的约束机制,从根源上避免竞态条件,比代码层面的检查更可靠。

如果一定要在代码中前置检查,可以用SELECT ... FOR UPDATE锁定查询行(若存在),但这种方式会影响并发性能,且依然不如唯一约束的可靠性高。

内容的提问来源于stack exchange,提问作者Evka'S

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:54:34