这篇文章,聊聊一个大家经常使用的编程模式 :Mybatis +「where 1 = 1 」。
笔者人生第一次重大的线上事故 ,就是和使用了类似的编程模式 相关,所以印象极其深刻。
这几天在调试一段业务代码时,又遇到类似的问题,所以笔者觉得非常要必要和大家絮叨絮叨。
1 OOM 事故笔者曾服务一家电商公司的用户中心,用户中心提供用户注册,查询,修改等基础功能 。用户中心有一个接口 getUserByConditions ,该接口支持通过 「用户名」、「昵称」、「手机号」、「用户编号」查询用户基本信息。
我们使用的是 ibatis (mybatis 的前身), SQLMap 见上图 。当构建动态 SQL 查询时,条件通常会追加到 WHERE 子句后,而以 WHERE 1 = 1 开头,可以轻松地使用 AND 追加其他条件。
但用户中心在上线后,竟然每隔三四个小时就发生了内存溢出问题 ,经过通过和 DBA 沟通,发现高频次出现全表查询用户表,执行 SQL 变成 :
查看日志后,发现前端传递的参数出现了空字符串,笔者在代码中并没有做参数校验,所以才出现全表查询 ,当时用户表的数据是 1000万 ,调用几次,用户中心服务就 OOM 了。
笔者在用户中心服务添加接口参数校验 ,即:「用户名」、「昵称」、「手机号」、「用户编号」,修改之后就再也没有产生这种问题了。
2 思维进化1、前后端同时做接口参数校验
为了提升开发效率,我们人为的将系统分为前端、后端,分别由两拨不同的人员开发 ,经常出现系统问题时,两拨人都非常不服气,相互指责。
有的时候,笔者会觉得很搞笑,因为这个本质是个规约问题。
要想系统健壮,前后端应该同时做接口参数校验 ,当大家都遵循这个规约时,出现系统问题的风险大大减少。
2、复用和专用要做平衡
笔者写的这个接口 getUserByConditions ,支持四种不同参数的查询,但是因为代码不够严谨,导致系统出现 OOM 。
其实,在业务非常明确的场景,我们可以将复用接口,拆分成四个更细粒度的接口 :
按照用户 ID 查询用户信息按照用户昵称查询用户信息按照手机号查询用户信息按照用户名查询用户信息比如按照用户 ID 查询用户信息 , SQLMAP 就简化为:
通过这样的拆分,我们的接口设计更加细粒度,也更容易维护 , 同时也可以规避 where 1 =1 产生的问题。
有的同学会有疑问:假如拆分得太细,会不会增加我编写 接口和 SQLMap 的工作量 ?
笔者的思路是:通过代码生成器动态生成,是绝对可以做到的 ,只不过需要做一丢丢的定制。
3、编写代码时,需要考虑资源占用量,做好预防性编程
笔者刚入行的时候,只是机械性的完成任务,并没有思考代码后面的资源占用,以及有没有可能产生恶劣的影响。
随着见识更多的系统,学习开源项目,笔者慢慢培养了一种习惯:
这段代码会占用多少系统资源如何规避风险 ,做好预防性编程。其实,这和玩游戏差不多 ,在玩游戏的时,我们经常说一个词,那就是意识。
上图,后裔跟墨子在压对面马可蔡文姬,看到小地图中路铠跟小乔的视野,方向是往下路来的,这时候我们就得到了一个信息。
知道对面的人要来抓,或者是协防,这种情况我们只有两个人,其他的队友都不在,只能选择避战,强打只会损失两名“大将”。
通过小地图的信息,并且想出应对方法,就是叫做“猜测意识”。
编程也是一样的,我们思考代码可能产生的系统资源占用,以及可能存在的风险,并做好防御性编程,就是编程的意识。
3 写到最后当我们在使用 :Mybatis +「where 1 = 1 」编程模式时,需要如下三点:
前后端同时做好接口参数校验 ;复用和专用要做平衡,条件允许情况下将复用 SQLMap 拆分成更细粒度的 SQLMap ;编写代码时,需要考虑资源占用量,做好预防性编程 ;文章片段推荐:
生命就是这样一个过程,一个不断超越自身局限的过程,这就是命运,任何人都是一样,在这过程中我们遭遇痛苦、超越局限、从而感受幸福。
所以一切人都是平等的,我们毫不特殊。
--- 史铁生
如果我的文章对你有所帮助,还请帮忙点赞、在看、转发一下,你的支持会激励我输出更高质量的文章,非常感谢!