昨晚, —— 不, 应该是今天凌晨, —— 注定是个不简单的时刻。 我入职后参与的第一个项目发布,而且自己所做的工作是数据订正,涉及用户数据的操作总是非常敏感而重要的。因此,心理还是比较紧张的。 果然是真实环境更能锻炼人。虽然事先检查了好几遍,可是实际执行时还是出问题了。
事情是这样的: 我写了几个 .py 脚本用来操作数据库,需要更新10000多个用户的资源记录,每个用户的资源记录涉及好几个数据库的几张表。 在具体执行时,发现每一个用户的资源更新都比较慢,——但实际上,仅从操作上来看并不能发现有哪个操作会比较耗时。 于是,大家立即聚拢查看日志记录(因此,这里显示了日志是多么的重要,尤其是数据库操作),发现有一条资源更新语句对于每一个用户都需要花费5s,而其它的资源更新语句合计对于每一个用户需要花费大约1s 。 简单地估算时间之后,就发现时间根本不够用。 我站在那里异常郁闷, 毕竟是在自己这里卡了壳,但没有料到是在一个似乎不起眼的地方出了错, —— 这又是一条教训: 重要的错误通常出现在不起眼的地方。
出问题的原因很简单: 在一张 10 万数量级的表中使用了子查询的更新语句, 每个更新语句执行要花费5s,有近10000条更新语句, 算一算,那得更新到何年何月!于是,立即成立“紧急应对小组”, 在预算时间之后,甚至都打算放弃此次发布计划了。 不过,最终还是找到了解决方案:
1. 将耗时很大的这条更新语句分离出去,生成SQL 语句执行。 对于剩下的部分, 每一个用户都需要 1s , 完全大约要2个多小时。 这也不够用。 由于每一个用户的资源记录更新几乎是相互独立的,于是,老板建议采用并发方案,将10000多个用户分割为10段, 使用同一脚本的多个 SHELL 进程分别订正。 这样大概几分钟就OK了。
2. 这个耗时很大的更新语句也是要执行的,怎么办呢? 于是,立即联系DBA, 他们对这条语句进行了优化, 用 两个 inner join 替换了子查询, 于是执行时间也降低为几分钟。 太强悍了! 不过,这也产生了一个问题: 为什么子查询的执行效率这么低 ?
教训、心得及体会:
1. 一定要做好适当的日志记录, 可以在出错的情况下寻找原因,或者其它有价值的线索;
2. 重大的错误或者过失,往往并不是发生在那些看似非常重要的地方,而会发生在不太起眼的地方;
3. 写程序(尤其是数据库操作)一定要考虑真实环境的约束,考察程序在真实环境下的有效性,否则,写出的程序可能是正确而无用的;
4. 重大的发布计划在事先一定要非常周密完善细致地布置好,以便在执行时尽量按部就班; 当然,意外无法避免,也要作好紧急应对准备;
5. 要学会编写高效的SQL 语句及 并发程序。
6. 研究子查询与内连接的执行效率;
7. 人际交往沟通异常重要,决不可忽视,更不可因自己是“程序猿”而忽视。