记一次多次智商下线导致的奇耻大辱
记一次多次智商下线导致的奇耻大辱
出来混,智商是关键,智商下线总会把一件简单的事情复杂化,导致花费更多的时间却做不好事情。最近刚好遇到这样的一次经历,记录下来,用来提醒自己。
第一次问题反馈
公司技服反馈,公司产品在某处使用过程中发生查询结果错的问题,可能涉及到保密问题,所以说的比较笼统,原文如下:
写了一过程,声明了一带参数的游标,限定了查询结果,可是运行发现这个游标查询的是一个全集。
第一次智商下线
简单的分析了一下反馈内容,脑海中简单回忆了一下,没有关于带参游标的概念,然后去查阅了一下公司产品的帮助文档,也没有发现关于带参游标的概念,于是想当然的认为对方的意思表达的有问题,可能是想说存储过程带参数,然后游标里面用到了这个参数作为限定条件,但是执行之后发现限定条件没起作用,导致结果多了。 没有用例,于是打算自己编写用例复现问题,然后分析原因。 用例如下:
create table test(col int);
insert into test values(1);
insert into test values(2);
insert into test values(3);
create or replace procedure sel(con in int) as
declare
cursor csr for select * from test where con = col;//查询表test的col列与过程sel的参数con一致的结果
ret sel % rowtype;
begin
open csr;
fetch csr into ret;
while csr % found loop
dbms_output.put_line(ret.col);
fetch csr into ret;
end loop;
close csr;
end;
select * from test; //预期结果3条1 2 3
exec sel(1); //预期结果只有一条 1
exec sel(2); //预期结果只有一条 2
exec sel(3); //预期结果只有一条 3
简单执行后,发现结果符合预期,所以算是复现失败,于是找到技服,进一步了解。
第二次智商下线
简单问过技服之后,技服说有过电话沟通,那边说是字符串类型。于是,第二次智商下线,简单的修改用例,改成如下用例:
create table test(col varchar(10));
insert into test values('1');
insert into test values('2');
insert into test values('3');
create or replace procedure sel(con in varchar(10)) as
declare
cursor csr for select * from test where con = col;//查询表test的col列与过程sel的参数con一致的结果
ret sel % rowtype;
begin
open csr;
fetch csr into ret;
while csr % found loop
dbms_output.put_line(ret.col);
fetch csr into ret;
end loop;
close csr;
end;
select * from test; //预期结果3条'1' '2' '3'
exec sel(1); //预期结果只有一条 '1'
exec sel(2); //预期结果只有一条 '2'
exec sel(3); //预期结果只有一条 '3'
用例执行,显然,结果依然符合预期,复现再次失败,仔细反思流程,终于觉得是不是忽略了用户所说的带参游标的问题,于是终于想起了度娘。百度发现带参游标是真实存在的,于是找到了一个简单的用例试验。 用例如下:
create or replace procedure a
as
cursor b(c_id int) is select * from d where id=c_id;
begin
open b(111);
end;
在自己那里试验了一下之后,发现语法总是通不过,于是再次想当然的认为用户所说的带参游标公司产品确实不支持,所以按照用户的说法第二套复现应该能够成功,但是仍然失败,怀疑是公司产品的一些周边产品的问题。想请技服再次与用户沟通,进一步的确定问题所在。
第三次智商下线
与技服沟通过程中,技服是个负责任的人,于是说,确定不支持带参游标么?于是百度了一下,把上面的用例执行在自己本地安装的公司产品上试验了一下,发现能够通过。好吧,这时才发现虽然公司帮助文档中没有提供说明,但确确实实支持带参游标这个功能,而我本地语法通不过是因为使用的是产品的某个分支版本,该版本不支持。 于是借用技服的机器构造用例:
create table test(col varchar(10));
insert into test values('1');
insert into test values('2');
insert into test values('3');
create or replace procedure sel(con in varchar(10)) as
declare
csr(csr_con varchar(10)) cursor is select * from test where csr_con = col;//查询表test的col列与游标csr的参数csr_con一致的结果,csr_con的值由con提供
ret sel % rowtype;
begin
open csr(con);
fetch csr into ret;
while csr % found loop
dbms_output.put_line(ret.col);
fetch csr into ret;
end loop;
close csr;
end;
exec sel('1'); //预期一条结果 '1'
exec sel('2'); //预期一条结果 '2'
exec sel('3'); //预期一条结果 '3'
执行之后,发现结果依然符合预期,说明复现依然失败,感觉凭目前的条件无法判断问题所在,于是请技服与用户再次沟通。
第二次问题反馈
依然是由于保密,因此这次沟通只得到了两张照片和一段话,由于没有找到合适图床,这里图片不贴出来。图片的内容中主要是过程的名和参数,以及游标的全部定义。
这个T_CLASSID = ‘AA’根据这三个参数进行查询的T_PNAME等参数结果并不相符,这个T_PNAME反查回去的classid不是AA。
第四次智商下线
由于用例和数据依然不完整,但有了大概的轮廓,因此依据这些进行了复现用例的书写:
create table rt_ttapall(rsltype varchar(10), snum number, pname varchar(10), palia varchar(10),
missid number, objid number, classid varchar(10),
subsys number);
insert into rt_tta_pall values('KA0', 0, 'KeyNo0', 'GNCC当',
11, 1, 'AA',
10);
insert into rt_tta_pall values('KA0', 0, 'KeyNo0', 'GNCC当',
11, 1, 'AA',
10);
insert into rt_tta_pall values('KA0', 0, 'KeyNo0', 'GNCC当',
12, 1, 'AA',
10);
insert into rt_tta_pall values('KA0', 0, 'KeyNo0', 'GNCC当',
11, 2, 'AA',
10);
insert into rt_tta_pall values('KA0', 0, 'KeyNo0', 'GNCC当',
11, 1, 'BB',
10);
create or replace procedure pro_in_telpara(t_telmissid in numeric(10, 0), t_telobjid in numeric(10, 0), t_classid in varchar(10)) as
declare
t_rslttype varchar(10);
t_snum number;
t_pname varchar(10);
t_palia varchar(10);
cursor cur_telpara(missno number, objno number, classid varchar(10)) is
select rsltype, snum, pname, palia
from rt_ttapall
where MISSID = missno and CLASSID = classid and OBJID = objno
order by subsys, snum;
begin
open cur_telpara(t_telmissid, t_telobjid, t_classid);
fetch cur_telpara into t_rslttype, t_snum, t_pname, t_palia;
while cur_telpara % found loop //输出所有符合条件的结果
dbms_output.put_line(t_rslttype + ' ' + t_snum + ' ' + t_pname + ' ' + t_palia);
fetch cur_telpara into t_rslttype, t_snum, t_pname, t_palia;
end loop;
dbms_output.put_line(cur_telpara % rowcount); //输出结果个数
close cur_telpara;
end;
--执行过程
exec pro_in_telpara(11, 1, 'AA');
--直接查询
select rsltype, snum, pname, palia
from rt_ttapall
where MISSID = 11 and OBJID = 1 and CLASSID = 'AA'
order by subsys, snum;
如上的用例,执行后期望两边执行结果一致,均为两条结果,即按照 11 1 ‘AA’三个条件筛选后的两条数据,但执行后发现select语句直接执行的结果正确,但存储过程执行后的结果确实多了一条,看起来非常像’AA’条件过滤失效或者条件丢失。于是再次想当然的认为成功复现了用户说明的问题,而且想当然的认为上面的两个用例的结果应该一致,通知技服给用户反馈,说已经成功复现,确实是我们这边的问题,马上着手修改。
第五次智商下线
问题成功复现,于是开始着手修改,首先分析是游标处理过程中将’AA’条件丢失,于是想要确认最终的执行计划上是否有该条件。但发现过程执行时后台不会输出对应的执行计划,没办法,想到先查看select的查询计划,期望游标方式也会生成对应的计划,看计划的差异。 select的计划很简单,这边执行的过程中是将三个值当作Const直接下放到底层的scan key,按照三个条件确定该算子的返回数据条数,然后上层sort算子执行order by过程。代码调试过程中,发现也确实是这样,三个条件均为Expr表达式,然后昨天是Var,右边是Const,认证了刚才的推想,那么也就是说,返回结果数是有scan key决定的,只要检查游标方式生成的scan key的条件哪里出了问题就可以了,在生成scan key的位置打了断点,进行调试。 调试的过程中,发现游标方式也会生成同样的scan key,但是上面的条件却不一致,11和1的条件与select的计划类似,一边是Var,另一边是Param。但是刚好’AA’条件不一致,是一个NullTest,也就是说只判断是否为空。于是再次想当然的认为之前的设想得到了验证,确实是计划生成过程中将’AA’条件丢失,而变成了NullTest,于是一步步倒推,希望找到问题发生的位置。 1 计划的生成位置,已经是NullTest了。 2 Query生成的为知,Expr已经变成空。 3 … 正在继续调试,以为马上就要找到问题原因的时候,技服接到用户电话,说在他执行的用例中把游标的参数改成classno,问题就没有了,没有了,没有了。 松了一口气的同时,却觉得怎么可能!改个名问题就解决了,和名字有什么关系! 于是回到刚才的用例,分析了一下游标部分,发现按照用户的操作之后,where条件MISSID = missno and CLASSID = classid and OBJID = objno会变成MISSID = missno and CLASSID = classno and OBJID = objno,区别只是classid变成classno,执行了一下,发现结果确实正确了。 那么,问题到底在哪呢?难道执行结果还和字段名有关?不可能,那还怎么用! 仔细分析一下,才回想起来我们的产品并不区分大小写,也就是说,MISSID = missno and CLASSID = classid and OBJID = objno实际上是MISSID = MISSNO AND CLASSID = CLASSID AND OBJID = OBJNO,中间的条件左右两边是一样的!那么是不是因为这个,导致条件变成了表中的字段CLASSID = 表中的字段CLASSID,横真,相当于没有条件。。。 于是果断将游标参数objno也改成了objid,发现该条件也会不起作用,而若游标参数与表中字段不一致时,执行结果均正确,好吧,问题确实是这个。 也就是说,这么漫长的用例复现和调试都是在做无用功,无用功,无用功!
总结
- 在无法获得具体用例时,自己书写用例或者复现的过程中,务必保证你的环境与用户的一致,或者差异绝对不会影响复现!避免多走弯路。
- 尽量与提出问题的人多做沟通,如果实在沟通不方便的话,那么一切以问题提出人的说法为标准。当然,问题提出者也有可能出错,除了客观实际的错误外,一切相信提出者的描述可以让你尽可能的理解别人的意思,而不会曲解别人的意思,导致走弯路。
- 在得到用户的用例时,一定要仔细分析用例想要的结果,并结合自己公司的产品确定用例是否会得到想要的结果。
后记
文中说明的问题为工作中遇到的某次问题的实际记录,这里记录下来是为了总结原因,给自己一个警钟。