那个阳光刺眼的周一,我坐在崭新的工位上手心有点出汗,这是我入职的第一天,我想给所有人留个好印象,组长走过来拍了拍我的肩膀,递给我一张任务单,他的语气很轻松,说小赵啊这个任务很简单,就是查一下用户列表然后把部门和角色信息补全,我一听心里乐开了花,这种增删改查的活儿我在学校里闭着眼睛都能搞定,我拍着胸脯说组长放心保证半小时内交货。
我回到座位打开电脑新建了一个Java类,我的手指在键盘上飞舞,很快我就写好了代码,我先调用userMapper的selectAll方法拿到了一个用户列表,然后我写了一个漂亮的for循环,在循环里我优雅地写下了两行查询,一行是deptMapper.selectById(user.getDeptId()),另一行是roleMapper.selectByUserId(user.getId()),我觉得自己聪明极了,这样代码多清晰啊,一看就明白每个用户是怎么补全数据的,我甚至还用了lambda表达式让代码看起来更现代,我美滋滋地运行了单元测试,测试数据只有五十条用户,唰一下就跑完了,全部通过,我得意地靠在椅背上,觉得自己真是个编程天才。
我兴冲冲地去找组长验收,组长当时正在和架构师讨论着什么分布式事务,他转过身来看我的代码,刚开始表情还挺温和,但当他看到那个for循环时,他的眉头慢慢皱了起来,就像看见了什么不该存在的东西,他的眼睛越瞪越大,嘴唇开始微微发抖,他扶了扶眼镜把脸凑近屏幕,仿佛不敢相信自己看到了什么,然后他深吸了一口气,那吸气的声音很长很长,长得让我开始不安。
你知道你在做什么吗,组长的声音听起来很平静,但那种平静底下好像压着一座火山,我点点头说知道啊,不就是补全用户信息吗,组长的手指开始敲击桌面,嗒,嗒,嗒,每一声都敲在我的心跳上,他突然提高了音量,你知道每一次数据库查询要经历什么吗,从应用程序到数据库驱动再到网络传输,经过TCP/IP协议栈,到达数据库服务器,数据库要解析SQL语句,生成执行计划,检查缓存,可能还要走索引,然后从磁盘读取数据,再通过网络传回来,每一次查询都是一次完整的旅程,而你,你在一个循环里发起这种旅行,一次,两次,五十次。
他的语速越来越快,你知道连接池吗,每个数据库连接都是珍贵的资源,就像高速公路上的车道,你一个人就占了五十条车道,其他车怎么办,紧急的支付请求怎么办,库存扣减怎么办,订单状态更新怎么办,它们全都被你堵在后面排队,你知道排队意味着什么吗,意味着超时,意味着失败,意味着用户支付成功了却看不到订单,意味着客服电话被打爆,意味着运营人员要加班写道歉信,意味着公司可能要赔钱。
而且这还只是五十个用户,组长的声音已经开始发抖了,如果是五千个用户呢,五万个用户呢,你打算发起五万次独立的查询吗,数据库服务器会怎么想,它会觉得受到了攻击,DDoS攻击,连接数瞬间爆满,CPU飙到百分之百,内存溢出,线程池耗尽,然后,砰,整个数据库宕机,所有线上服务全部停摆,用户打不开页面,商家无法管理商品,骑手接不到订单,公司每分钟损失的钱够给你发十年工资。
你以为这就结束了吗,组长冷笑着,数据库宕机会触发监控报警,凌晨三点运维兄弟的手机开始狂响,他不得不从床上爬起来,睡眼惺忪地打开电脑,一边骂娘一边尝试重启数据库,重启之后发现主从同步断了,又要手动修复数据,折腾到天亮才勉强恢复,而这一切的根源,就是你写的这个看似无害的for循环,就因为这几行代码,一个团队彻夜未眠,公司损失了真金白银,而你还在梦里流口水。
我的脸开始发烫,汗水顺着后背往下流,办公室安静得可怕,我能听见自己的心跳声,砰,砰,砰,周围的同事都停下了手里的工作,有人偷偷往这边看,有人假装在喝水但耳朵竖得老高,对面那个资深架构师摇了摇头,发出一声轻轻的叹息,那叹息比骂我还让我难受。
组长终于停了下来,他靠在椅子上看着天花板,好像在努力平复情绪,过了好一会儿他才重新开口,声音疲惫了很多,你知道该怎么写吗,我摇摇头,他说你应该用关联查询,一次把用户部门角色全部查出来,或者先批量查询部门数据再批量查询角色数据,然后用Map做映射关系,在内存里做数据组装,这样无论有多少用户,你都只需要两到三次数据库交互,而不是N次,这才是程序员该有的思维,要有全局观,要考虑到性能,考虑到扩展性,考虑到你写的每一行代码都可能运行在生产环境,服务着成千上万的用户。
他把我的代码全部删掉,新建了一个文件,手指在键盘上敲出清脆的响声,他写了一个包含join的SQL语句,又写了一个批量查询的方法,然后用了Java8的Stream和Collectors.groupingBy,短短二十行代码,优雅得像一首诗,性能比我那个版本快了一百倍都不止,我看呆了,原来代码可以写得这么美。
那天下午我坐在工位上发呆,脑子里全是组长说的那些话,连接池,网络IO,数据库负载,生产事故,我第一次意识到编程不是只要功能实现就行,每一行代码都有重量,都可能产生蝴蝶效应,从那天起我再也没在循环里写过数据库查询,每次写代码前我都会问自己,这样写会不会有性能问题,有没有更好的方法,那个挨骂的下午成了我职业生涯中最重要的一课,虽然当时难堪得想找个地缝钻进去,但现在回想起来,我真心感谢组长那顿劈头盖脸的教训,他骂醒了一个只会写功能的新手,培养出了一个开始思考性能的程序员。