1
临时表只作用于一个session中!
临时表只作用于一个session中!
临时表只作用于一个session中!
重要的事情先说三遍。
这两天被临时表坑得有点惨。其实说白了,还是自己平时没有注意总结,对临时表,物理裸表就没有一个十分清晰的概念。以致误把物理表当临时表用,用临时表时不注意其作用范围,才造成这么多本不该出现的错误。
在informix里创建临时表主要有显式和隐式两种,隐式创建临时表语法是:
select * into temp table ( with no log);
显式一般使用
create temp table (
……
);
但不管创建的临时表是显式的还是隐式的,其都只能作用于一个会话中,会话结束,临时表自动释放。所以如果像下面这种写法,是会报错的:
dbaccess cbs <<!
select * from table1 where 1 != 1 into temp tmp_table1;
!
dbaccess cbs <<!
insert into tmp_table1 select * from table1;
!
报错原因是在下面一个会话中,找不到临时表tmp_table1,因为第一个session已经结束,tmp_table1就已经被删除。
那么,什么是session?
通俗点讲,session就是一段通信从开始到结束,反映在数据库中,也就是数据库从连接上到断开连接之间的SQL操作。所以上面的临时表要想正确运行,必须在一个会话里完成,即:
dbaccess cbs <<!
select * from table1 where 1 != 1 into temp tmp_table1;
insert into tmp_table1 select * from table1;
!
那么如何看临时表对进程并发的影响?
首先要明确一点的是,在实际的程序开发中,对数据库的操作基本都是包含在事务中的,因为事务本身具有隔离性,所以在各自的事务里创建临时表,它只会各自的事务里创建属于自己的临时表空间,相互之间是不会影响的。
这也就意味着,假如我们有一个调度程序demoA,demoA里包含了一个功能程序 demoB,然后在 demoA 里begin了一个事务,在 demoB 里创建了一个临时表。那么我们通过运行调度程序demoA去调用 demoB 来创建临时表,单进程运行,肯定是没有问题的。
如果我们开100个窗口,同时运行demoA,那么就向当于同时起了100个进程,也就对应了100个事务,相互之间仍是没有影响的,100个临时表都会创建成功,当程序结束时,100个临时表的表空间都会被释放掉。
可是如果我们的调度规则是这样的:
现在有一个demoC,它也能调度demoB程序,但是它厉害的地方在于,demoC可以在程序内创建子进程,每次最多可以支持30个进程并发,当所有进程都运行完成后通过wait回收子进程资源。
接下来我们用demoC来调起100个demoB,由于每次最多只能调30个,所以只有等这30个demoB运行结束了,才能继续运行剩余的demoB,循环多次,才能将100个demoB调用完。那么在这种机制下,会不会有问题呢?
由于在第一次起30个进程的时候,也是开了30个会话,begin了30个事务,彼此之间不会互相影响,但是当第一轮的30个demoB结束,调用第二轮30个demoB的时候,由于仍在demoC开启的30个子进程里,虽然事务已经commit了,但是会话没有结束,demoB再次创建临时表的时候,就会报sqlcode-389,即要创建的临时表已经存在。
事务关闭不会释放临时表,只有当会话结束时临时表才会被删除。
那么有没有什么其他方法删除创建的临时表呢?
当然是有。除了关闭session,我们仍可以通过显式的方式删除临时表,即如下的语法:
drop table tmp_table1;
当然在并发程序里创建物理表的情节要更严重,之前在写程序的时候创建了物理表之所以没报错,主要还是因为不存在并发的问题,但这样写肯定是存在很严重的安全隐患的。所以我们在写代码的过程中,能创建临时表的就不要去创建物理表,而且最好要养成创建了临时表在使用完之后及时删除的好习惯。
2
其实何止是临时表,在这之前,因为游标的错误使用,导致程序陷入死循环,这也是一个不得不防的陷阱。
当时的使用场景是这样的:
我在程序里通过声明游标去查表,查询sql带了一堆where条件(还是个有union的超级复杂的sql),然后通过循环fetch游标里的数据,进行某些字段的update后重新插入到该表中去,当时程序写完测试时没有任何问题,但是在之后的一次测试过程中,发现程序陷入死循环直接动不了了,排查才发现,原因是在我重新插入数据到表里的时候,由于新插入的数据同样符合声明游标的where条件,导致fetch的时候会不断取到新插入的数据,导致程序陷入死循环。
其实解决这类死循环的方案有很多,比如可以在处理完的数据记录中在某个字段上打上标志位信息,在声明游标时通过该标志位过滤一遍,或者创建一张与该表结构完全一模一样的临时表,先将数据插入到临时表,等全部处理完成后,再将临时表数据导入到该表中。(我当时就是采用了第二种方案,才导致了第一节中提到的临时表问题,晕~)
据一位大神说还有一种方法是申明静态游标,我尝试了几次没有成功,也就放弃了。
这个教训告诉我:在操作游标的时候,不要自己给自己下套。
接下来谈谈这段时间我在代码审核过程中遇到的一些游标上的使用问题。
声明游标的时候加with hold for 有什么作用?
常规的cursor在遇到事务commit或者rollback的时候是会自动关闭的,加了with hold for 就可以有效避免这个问题,我们在声明游标的时候都知道要这么写,但很少有人去深究这么写的作用。
声明游标在什么时候要加for update?
声明游标的时候,for update是手工加锁,也就是说,在你操作游标的数据库表的时候,这张表是被锁住的,只有当游标释放后锁才会相应释放。这样做的好处显而易见,并不会因为在你操作这张数据库表的时候有其他的人对这张表进行update等操作导致程序出现问题,但是同时也要注意到,因为在手工加锁的过程中,别人是不能对该表进行操作的,如果这时候有人想要查询一下这张表,因为每个sql都有默认的wait mode,当等待时间到了数据库仍然被锁,就会报锁表错误,这样就会造成别的程序错误。
所以,如果声明的游标里的操作比较简单,数据量少,或者耗用时间比较短,最好是加上 for update,特别是要对数据库表进行很重要的update操作,就一定要加上for update;而如果游标里的操作非常复杂,而且数据量庞大,一次游标操作动辄几分钟,如果能不加锁最好就不要加锁,以免影响到别的sql对这张表的操作。
程序return的时候一定要记得检查游标是否关闭和释放。
除了正常的fetch到最后一条数据了,游标的正常释放外,如果在fetch数据进行操作的过程中,程序直接return了,一定要记得关闭和释放游标。虽然即使不关闭游标,很大几率程序并不会报错,但这是代码安全规范所不允许的,我们在日常程序开发中就要养成良好的编码习惯。
另外,需要说明一点的是:游标只能关闭一次,但是可以释放多次,并不会报错。
3
可以说,这段时间遇到了我们开发小组成立以来最大的滑铁卢。
近期开发的几个程序,几乎每一个都存在或多或少的问题。这在之前几乎是未曾有过的事,我们小组是出了名的效率高,错误少甚至几乎没有错误。但这次这么多问题一次性集中爆发出来了,足以引起我们的警醒。
虽然说存在着很多客观的原因,比如开发时间紧,代码逻辑复杂,其他需求的不断插入等等,但如果以这些作为自我安慰,推脱责任的理由,未免显得太底气不足。说到底还是平时思考问题的时候想得太少,不够全面,太过于粗心。
于我自己来说,我觉得更有不可推卸的责任。以往代码质量高,极少出错误,是因为组里有两位经验丰富,技术深湛的组长全局把控。这次需求开发时间本来就不怎么充裕,无巧不巧的是,两位组长因为身体原因经常同时请假,组里的日常工作分配或多或少都是我在协助推进的,包括系统测试。
虽然说系统测试做了两轮,但因为关注点不够(比如第一轮测试时只注意自己的程序的案例覆盖,第二轮测试也是因为各种杂七杂八的事情掺杂,测试过程中对代码又进行多次修改,回归测试没有做好,虽然关注到了效率,但是并没有引起重视),导致测试的结果并不具有参考性,在程序的逻辑、性能等方面仍存在许多缺陷没有发现。
其实说到底还是自己太嫩,想问题不够全面,自以为在领导不在的时候能够站出来承担起一部分责任,可没想到第一次主动承担责任就暴露出这么多问题。虽然说以前自己有过一些管理经验,但与开发管理毕竟是两回事;尽管目前自己正在备考高级项目管理师,但从学会到学以致用的这个过程,我要学习的东西还有太多太多,要走的路还很长很长。
这个教训或许值得我铭记很久很久,就像上面的两个错误,临时表和游标,足以刻骨铭心。
4
但是不是这样就完了?错误修复,测试通过,这件事就翻过去了呢?
设若真如此,这次教训恐怕就白白教训了。举一反三,为什么会出现这样的错误?错误修复的过程中会不会带来新的问题(比如临时表的问题就是修复游标的错误引起的)?当初在设计开发方案的时候为什么不能思虑周全,甚至出了问题后补救措施也不能顾及周全?代码写完之后的评审是不是不到位?测试的关注点是不是没有覆盖全面?乃至整个开发流程是不是都有可能出了问题?
凡此种种,或许都值得反思。
比如说临时表的问题,我记得曾经有同事总结过临时表使用注意事项的PPT,事后我查看别人的代码在创建临时表的时候,都有手动drop的动作,可我偏偏都没有去多看一眼,而是选择了过分的相信自己。只能说自食苦果,这又能怪谁呢?
好在,这些问题尚未造成不可挽回的损失,但却给我们每一个人敲响了警钟。开发是一项细致的工作,只有流程规范,严格自律方能战无不胜。一次失误或可原谅,但若将这种不良的习惯沿袭到今后的开发中,谁又敢放心的将开发工作交给我们?
今晚写了很多,也反思了很多。但我想,在我们每个人成长的过程中总会遇到许许多多的风雨挫折,只要善于总结,勇于担当,总有一天会长大。而这,就当是自己太年轻,所必须要付出的代价吧!
2018-04-04,禹鼎侯于上海。
网友评论