在写这篇文章之前,最想提醒大家的是,开发一定不能想当然,看着没问题就不调试了,结果它就是有问题的。如果时间很紧,到了测试阶段才发现问题解决问题那就很狼狈很被动了,不要问我为什么会特别想提这个。
SQL 聚合函数SUM值为NULL引发的”爆炸“。
背景
场景是这样的,一个简单的功能实现,直接以SQL语句依靠MYSQL DB直接来实现并发锁(关于锁不理解可以看这里)。语句是这样的:
update tbGift2 a, (select sum(iValue) as cnt from tbGift2 where iHostUin = '327693651' and iStatus = 1) b, (select count(*) as cnt from tbGift2 where iReceiveUin = '318713931') c set a.iStatus=1,a.iValue=2,a.iReceiveUin=3187139331, a.dtReceiveAt = now() where a.id =3 and iStatus = 0 and 8>= b.cnt+2 and 2>c.cn;
这句语句的大概意思是,要更新表数据,并且聚合sum值+2要小于等于8,count值要小于2条。咋一看,这语句没毛病吧。看起来确实也没什么问题,where条件限制了并发的发生。但是,奇怪的事情发生了,这个语句执行是不成功的!!然后代码功能就自爆了。
现场勘察
小马排查了语法,然后单独去掉8>= b.cnt+2的条件,既然执行成功了。所以这个条件很可能是false。查一下select sum(iValue) as cnt from tbGift2 where iHostUin = '327693651' and iStatus = 1语句的结果,既然是NULL。是的,你没有看错,当没有聚合记录的时候这个值就是NULL而不是0,尽管我的iValue字段是int型。NULL又如何0又如何?是的,SQL这里还有个坑,NULL是不能做比较的,只能用ISNULL,IFNULL等,用NULL作为比较的话,尽管是NULL==NULL,结果也是false。这可不是PHP哦,比较能自动转换类型。就算NULL+1=也是等于Null
解决
解决方案参考:
1、使用isnull()方法进行判断,如isnull(sum(cnt),0),其中0为默认值,如果sum(cnt)的结果为null,则赋值 0;同理此方法可用于某字段,isnull(cnt,0),这样就可以排除一些值为null的数值型字段了。而如果这样 isnull(sum(isnull(cnt,0)),0),这样就可保万无一失了。
2、使用case when else流程控制语句,如可以用"case when sum(cnt) is null then 0 else sum(cnt) end"来代替sum(cnt),其中0同上一条的意义相同。
解决后的语句:
update tbGift2 a, (select COALESCE(SUM(iValue),0) as cnt from tbGift2 where iHostUin = '327693651' and iStatus = 1) b, (select count(*) as cnt from tbGift2 where iReceiveUin = '318713931') c set a.iStatus=1,a.iValue=2,a.iReceiveUin=3187139331, a.dtReceiveAt = now() where a.id =3 and iStatus = 0 and 8>= b.cnt+2 and 2>c.cn;
执行正常。总算可以倒杯咖啡喝喝了。
别说这是我的BUG