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
相关产品推荐
相关产品推荐

