returning ... into ... 有没有并发风险
比如有一个单据表,如果状态为“未审核”,那么就允许删除;如果为“已审核”,那么就不允许删除;
所以对于删除这个功能,我们可以写成一下的存储过程:
create procedure Del_Order
(P_单号 varchar2)
is
v_状态 varchar2(20);
begin
begin
select 状态 into v_状态 from 单据表 where 单号=p_单号;
exception
when_no_data_found then
dbms_output.putline('单号'||p_单号||'不存在');
return;
end;
if v_状态='未审核'
then
delete from 单据表 where 单号=p_单号;
dbms_output.putline('单号'||p_单号||'删除成功');
else
dbms_output.putline('单号'||p_单号||'已审核,不能删除');
end if;
end;
以上方法,可能很多人都用到; 但是我认为 很危险;
因为 这里是:先通过查询判断状态,再执行删除操作;再这两个操作之间,可能状态已经发生变化,所以可能导致并发异常;
所以,以上是一个危险的写法;
可以这样改进:
把第一句查询 : select 状态 into v_状态 from 单据表 where 单号=p_单号;
后面加一个 for update
这样可以锁住,就不会导致并发异常;
但是我觉得该方法不好;
有经验的开发人员,对于pl/sql最好做“进攻”式编程;
所以,我的一般风格是:
create procedure Del_Order
(P_单号 varchar2)
is
v_状态 varchar2(20);
begin
delete from 单据表 where 单号=p_单号 and 状态='未审核';
if sql%rowcount>0
then
dbms_output.putline('单号'||p_单号||'删除成功');
else
begin
select 状态 into v_状态 from 单据表 where 单号=p_单号;
exception
when_no_data_found then
dbms_output.putline('单号'||p_单号||'不存在');
return;
end;
dbms_output.putline('单号'||p_单号||'已审核,不能删除');
end if;
end;
该方法,我觉得有两个优点:
1.提高运行效率,如果大部分情况下,都是执行成功的,那么可以少做一步判断;
2.不会有并发风险;
下面是第3种方法,用returning :
create procedure Del_Order
(P_单号 varchar2)
is
v_状态 varchar2(20);
begin
delete from 单据表 where 单号=p_单号
returing 状态 into v_状态;
if sql%rowcount>0
then
if v_状态='未审核'
then
dbms_output.putline('单号'||p_单号||'删除成功');
else
rollback;
dbms_output.putline('单号'||p_单号||'已审核,不能删除');
return;
end if;
else
dbms_output.putline('单号'||p_单号||'不存在');
end if;
end;
如果从写代码角度看,第3种方法代码最少,如果没有问题,第 3种为首选,
不过与第2种方法比,我觉得有两个缺点:
1.有可能有并发风险, delete from 单据表 where 单号=p_单号 returing 状态 into v_状态 这句话能保证
返回的状态一定是删除后的状态,但想想也不可能,删除后怎么再去取状态,肯定是先取状态,再删除,那么就有个疑问,
万一先取的状态是“为审核”,但是正在执行的时候就是“已审核”,这样就会把不允许删除的也删掉了,产生和第一种方法一样的并发异常,因为“delete from 单据表 where 单号=p_单号 returing 状态 into v_状态" 这句话同时做两件事情,所以不知道是否有并发异常的风险,也不容易测试;我猜测是没有并发异常的;但是对于 delete .. returing ..into...这种写法,毕竟是一句话做两件事情,谁知道它内部是如何执行的呢?我们是否可以把它理解成“select ..from ..for update; delete from .. ”这两句话的合并呢?
2.如果大部分情况都是删除成功的,那么第2种方法效率高,不需要再去执行查询状态的操作;但是第3种方法代码少,如果没有并发风险,也值得推荐。
所以我这个帖子其实有两个问题:
1. 对于删除单据这样一个简单操作,最好的写法风格是什么(结合 稳定,高效,代码少综合考虑)?
2. delete .. from .. returing ... 这一句话,可以理解成哪两句话的合并?有没有并发异常问题?
所以对于删除这个功能,我们可以写成一下的存储过程:
create procedure Del_Order
(P_单号 varchar2)
is
v_状态 varchar2(20);
begin
begin
select 状态 into v_状态 from 单据表 where 单号=p_单号;
exception
when_no_data_found then
dbms_output.putline('单号'||p_单号||'不存在');
return;
end;
if v_状态='未审核'
then
delete from 单据表 where 单号=p_单号;
dbms_output.putline('单号'||p_单号||'删除成功');
else
dbms_output.putline('单号'||p_单号||'已审核,不能删除');
end if;
end;
以上方法,可能很多人都用到; 但是我认为 很危险;
因为 这里是:先通过查询判断状态,再执行删除操作;再这两个操作之间,可能状态已经发生变化,所以可能导致并发异常;
所以,以上是一个危险的写法;
可以这样改进:
把第一句查询 : select 状态 into v_状态 from 单据表 where 单号=p_单号;
后面加一个 for update
这样可以锁住,就不会导致并发异常;
但是我觉得该方法不好;
有经验的开发人员,对于pl/sql最好做“进攻”式编程;
所以,我的一般风格是:
create procedure Del_Order
(P_单号 varchar2)
is
v_状态 varchar2(20);
begin
delete from 单据表 where 单号=p_单号 and 状态='未审核';
if sql%rowcount>0
then
dbms_output.putline('单号'||p_单号||'删除成功');
else
begin
select 状态 into v_状态 from 单据表 where 单号=p_单号;
exception
when_no_data_found then
dbms_output.putline('单号'||p_单号||'不存在');
return;
end;
dbms_output.putline('单号'||p_单号||'已审核,不能删除');
end if;
end;
该方法,我觉得有两个优点:
1.提高运行效率,如果大部分情况下,都是执行成功的,那么可以少做一步判断;
2.不会有并发风险;
下面是第3种方法,用returning :
create procedure Del_Order
(P_单号 varchar2)
is
v_状态 varchar2(20);
begin
delete from 单据表 where 单号=p_单号
returing 状态 into v_状态;
if sql%rowcount>0
then
if v_状态='未审核'
then
dbms_output.putline('单号'||p_单号||'删除成功');
else
rollback;
dbms_output.putline('单号'||p_单号||'已审核,不能删除');
return;
end if;
else
dbms_output.putline('单号'||p_单号||'不存在');
end if;
end;
如果从写代码角度看,第3种方法代码最少,如果没有问题,第 3种为首选,
不过与第2种方法比,我觉得有两个缺点:
1.有可能有并发风险, delete from 单据表 where 单号=p_单号 returing 状态 into v_状态 这句话能保证
返回的状态一定是删除后的状态,但想想也不可能,删除后怎么再去取状态,肯定是先取状态,再删除,那么就有个疑问,
万一先取的状态是“为审核”,但是正在执行的时候就是“已审核”,这样就会把不允许删除的也删掉了,产生和第一种方法一样的并发异常,因为“delete from 单据表 where 单号=p_单号 returing 状态 into v_状态" 这句话同时做两件事情,所以不知道是否有并发异常的风险,也不容易测试;我猜测是没有并发异常的;但是对于 delete .. returing ..into...这种写法,毕竟是一句话做两件事情,谁知道它内部是如何执行的呢?我们是否可以把它理解成“select ..from ..for update; delete from .. ”这两句话的合并呢?
2.如果大部分情况都是删除成功的,那么第2种方法效率高,不需要再去执行查询状态的操作;但是第3种方法代码少,如果没有并发风险,也值得推荐。
所以我这个帖子其实有两个问题:
1. 对于删除单据这样一个简单操作,最好的写法风格是什么(结合 稳定,高效,代码少综合考虑)?
2. delete .. from .. returing ... 这一句话,可以理解成哪两句话的合并?有没有并发异常问题?
===========================================================
第二个问题:
可以把Oracle的SQL语句理解为"原子"操作,不用担心在语句执行内部会有别的操作打断
实际上语句执行的整个过程中, 看到的都是语句执行前的数据的快照, 不管别的进程执行过什么操作,它都是看不到的。 这个对于所有DML语句都是一样的,比如说select语句:
你执行一个耗时长的select, 在执行过程中别的进程修改了你将要查询的数据, 但是你的select语句是不会看到这个更改的。
有关这个问题, 建议你多看看oracle关于undo方面的文章。
第一个问题:
节省行数没有任何意义, 不断的有意删除数据然后再回滚大大的增加了数据库的负担, 第3种方法不提倡。
第1种方法带上for update 与第二种方法都没问题。 个人喜欢用第二种方法。
可以把Oracle的SQL语句理解为"原子"操作,不用担心在语句执行内部会有别的操作打断
实际上语句执行的整个过程中, 看到的都是语句执行前的数据的快照, 不管别的进程执行过什么操作,它都是看不到的。 这个对于所有DML语句都是一样的,比如说select语句:
你执行一个耗时长的select, 在执行过程中别的进程修改了你将要查询的数据, 但是你的select语句是不会看到这个更改的。
有关这个问题, 建议你多看看oracle关于undo方面的文章。
第一个问题:
节省行数没有任何意义, 不断的有意删除数据然后再回滚大大的增加了数据库的负担, 第3种方法不提倡。
第1种方法带上for update 与第二种方法都没问题。 个人喜欢用第二种方法。