青云Oracle(4)

2019-04-17 14:58

程序给你写复杂了,增加了逻辑的复杂度,增加了潜在bug的可能性,容易产生并发造成的脏数据;而且会因为多了很多锁影响其他会话的执行;有百害而无一利;

当然我只是抛砖引玉,其实很多技巧都可以避免游标的时候,或减少游标嵌套的层数;

c. 游标有时“高不成低不就”,我在pl/sql用的稍微复杂些的算法,比如回

溯,递归等,鉴于游标只能前后滚动,而且没有想数组那样的按照行

号来回跳转,所以,必须把这些记录值存在数组后才能做灵活的处理; d. 游标效率低,这个表面上到无所谓,慢就慢点,但是如果高并发,就

增加了死锁的几率;

2.3写update语句的注意点; 2.3.1 “进攻式”编程:

我们在写update 语句的时候,有时候未必需要“真写”,

常见的例子是:单据审核,比如把单据状态从0变成1;(0-初始 1-审核 2-完成)

很多人的写法是:

Begin

Select 单据状态 into v_单据状态 from 单据表 where 单号=’123’; If v_单据状态=’1’ Then

dbms_output.put_line (‘单号123已经审核’); return; End if;

Update单据表 set 单据状态=’1’ where单号=’123’; dbms_output.put_line (‘单号123审核成功’);

…………… End;

这样的写法看上去很符合人的逻辑:先判断是否已经审核了,如果已经审核了,就不要做了,如果还没有什么,那么就打上标志;

可是漏洞很大:还是我上面说的“刻舟求剑”;

因为这段代码主要有两句话,先查询状态,再更改状态;

但是这两句话有时间差,第二句话执行的时候,第一句话的查询结果可能发生;也就是说在高并发的情况下,该程序可能得到错误的结果;

怎么去保护呢,很自然,有人想到了:

Select 单据状态 into v_单据状态 from 单据表 where 单号=’123’; 这句话后面加上 for update ;的确,加上这个确实让代码“安全”了;

但是这样,如果高并发,虽然运行不会出逻辑问题,但是如果100人同时执行,执行一次需要0.1秒,那么最终全部完全的时间是10秒;因为这里是串行的,每个人执行到for update 这里就会排队依次执行;

所以最好这样写: begin

Update单据表 set 单据状态=’1’ where单号=’123’ and 单据状态=’0’; If sql%rowcount=0

then

dbms_output.put_line (‘单号123审核成功’); else

dbms_output.put_line (‘单号123已经审核’); return; end if; …………… End;

这种写法“一石二鸟”,一条update语句既可以作为更新,又含有了检查的功能; 通俗的说:要更新什么,就在where里面写上它更新前应该的状态;

这样如果有100个人同时执行的时候,只有第一个人顺利执行,其他人等第一个花0.1秒提交后,全部是sql%rowcount=0不会引起锁,所以就不会有串行执行的互相等待;

这种以Update 兼容select 的做法叫做“进攻式”编程,这个术语,好像是有一本叫做“SQL编程艺术”里提出的;原理就是避免或减少“防御性”的判断,直接做数据的处理;好处是提供并发性,简化程序,防止脏数据;

顺便说一句:

有的人防止并发异常,自作聪明的“发明”了一种写法;

你不是怕并发异常吗?所以他在过程的第一句就来上:

Update 单据 set memo=memo where 单号=’123’; –-随便找个字段“自己”更新“自己” 这样的目的就是为了“锁住”,多个人同时执行的时候不会有数据异常;

我以前遇到一个合作伙伴的项目,每个过程前的第一句话,都来上这种“什么也不做,就是要锁你”这样的怪语句,如果真的要锁,你还不如写成;

Select … into .. from 单据 where 单号=’123’ for update

至少这样的语句还容易理解,而你来个 update… set 自己=自己 where..;这让人看了很“费解”(也许他不知道for update );

于是我就问他为什么这样做,他还理直气壮的跟我说,这是他们团队的一个“高手”想出的“妙招”,用来防止多个人同时做一件事情发生的数据异常;我不怀疑这个高手的智商,可是这些高手难道就不能多看点书,遇到问题就会出“怪招”,还自鸣得意;

而且每执行一个update语句,都会产生日志的,你就是 update 自己=自己,看上去啥也没干,那还是要生成修改前和修该后的日志,这些无谓的日志去消耗干嘛呢;

如果有兴趣查看日志的消耗,我写了一段脚本,有兴趣的试试:

DECLARE

V_VALUE1 PLS_INTEGER; V_VALUE2 PLS_INTEGER; begin

-- 记录执行前的redo size ; SELECT A.VALUE INTO V_VALUE1

FROM V$MYSTAT A, V$STATNAME B WHERE A.STATISTIC# = B.STATISTIC# AND NAME = 'redo size';

--------------------------------

--写上你的update 语句,哪怕update 自己=自己 ,也会生成日志; COMMIT;--及时把redo buffer 的数据写会redolog文件;

-------------------

-- 记录执行后的redo size ; SELECT A.VALUE

INTO V_VALUE2

FROM V$MYSTAT A, V$STATNAME B WHERE A.STATISTIC# = B.STATISTIC# AND NAME = 'redo size';

dbms_output.put_line('执行前redo size:' || to_char(V_VALUE1));

dbms_output.put_line('执行后redo size:' || to_char(V_VALUE2));

dbms_output.put_line('增加的redo size:' || to_char(V_VALUE2 - V_VALUE1)); end;

2.3.2 update带返回值:

这个“功能”非常实用,如果不知道这个功能,pl/sql算白学了, 不能说t-sql没有这样的功能,你就不知道用;

就像老外到中国不去长城一样;

该功能的表达方式如:

Update table1 set field2=xxx where field1=xxx Return fiend1,field2,field3 into v1,v2,v3;

或者批量(多条记录):

Update table1 set field2=xxx where field1=xxx

Return fiend1,field2,field3 BULK COLLECT into v1,v2,v3;--变量是数组类型;

这样可以省略很多select 代码;这个倒无所谓,有的时候,update之后,再想selelct 不是那么方面的;

比如:Update table1 set field2=(selelct … from …) where field1=xxx Return field2 into v2; 这个就非常方便;

这个功能如果仅仅是为了减少部分select,那到不算什么大特色;相对t-sql,也只不过方面些而已;

但有的时候,对于高并发的处理,它有很多妙用;

比如:仓库入库,

在我们在入库前,就要做货位分配处理,假如有100托货,同时入库,入库的原则很简答,就是找最小货位;你怎么保证着100托的货同时瞬间分配而不会互相干扰;

这个很简答的问题,一般人都这样写:

Begin

Select min(货位) invo v_货位 from 货位表 where 货位状态=’空闲’; Update 货位表 set 货位状态=’已分配’ where 货位=v_货位; dbms_output.put_line('分配货位成功,是:’|| v_货位); End ;

但是这样写,只能一个一个的按顺序分配,如果100个人同时执行这样的分配工作,那么这些货位都“打架”了;

所以,这个问题,我的最初的写法是: Begin

For I in 1..10

Loop

Select min(货位) invo v_货位 from 货位表 where 货位状态=’空闲’; Update 货位表 set 货位状态=’已分配’ where 货位=v_货位; If sql%rowcunt=1 then

Begin

dbms_output.put_line('分配货位成功,是:’|| v_货位); exit; end if; end loop; End ;

就是给它10次机会,如果并发太高,每次找到最小货位后,就被别人“捷足先登”,那只能多试几次了;

总有一次能成功把;当然,如果胆子大一点,可能给“无限”的尝试次数,不管这样,这种写法很怪;

有什么好的解决方法吗,这里就用到了update返回值的方法; Begin

Update 货位表 set 货位状态=’已分配’ where 货位=(Select min(货位) from 货位表 where 货位状态=’空闲’) where 货位状态=’空闲’) returning 货位 into v_货位; dbms_output.put_line('分配货位成功,是:’|| v_货位); End ;

这样即使100个人同时分配,也不会分到一起的,说老实话,如果不是用这个update …return into .. 的技巧,我还真没想到有什么特别好的方法来处理这个问题;

有的人会怀疑,该写法是不是“安全”的,我经过很多测试,确保是安全的,

大家仔细看这句话:

Update 货位表 set 货位状态=’已分配’ where 货位=(Select min(货位) from 货位表 where 货位状态=’空闲’)

AND 货位状态='空闲'; returning 货位 into v_货位;

这句话里,虽然只是操作一个表,但是其实分两部分,

第1部分:Select min(货位) from 货位表 where 货位状态=’空闲’

第2部分: Update 货位表 set 货位状态=’已分配’where 货位=(第1部分) AND 货位状态='空闲' 也许有人有会疑惑,会不会在高并发的的时候出现这种情况:

第1部分先得到了一个最小货位 001,执行到第2部分的时候,001 的状态已经不是“空闲”,因为第1部分没有锁;

当第2部分执行的时候,可能得到的sql%rowcount=0 ;

我个人认为这种情况不可能,oracle的运行机制可能是这样的: 假如两个人(不同的session)同时执行这句嵌套的update ;

那么同时得到的最小货位肯定一样,其中一个先更新,这时候在提交前会锁住该货位;

另一个发现该货位的记录锁住了,就整个放弃,等你完成,我在重新执行这个SQL,于是它会不停地重新执行这条语句,直到前一个人提交了,这时候它检索的min(货位)也不可能是001货位了,而可能是002货位;

我个人的理解是:对于一条SQL,无论该SQL多么复杂,在执行的过程中,它内部可能要分好几步去执行,但是只要有一步被锁住,那么它前面做好的几步会全部作废,全部重头再执行,如果执行到某一步,发现又有锁了,那再重新全部回头再做,以此循环,直到成功。

这就类似于做一件高级的陶瓷艺术品,有很多工序,中间某个烧火的工序时间控制错了,那么前面的工序全部作废,重头再来;

如果这种原理,就非常安全;


青云Oracle(4).doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:18版高考语文二轮复习考前三个月第四轮基础组合练21

相关阅读
本类排行
× 注册会员免费下载(下载后可以自由复制和排版)

马上注册会员

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: