An image to describe post 杂忆数据库旧事

2019年,越南,会安

上周在介绍极客时间课程时提了点数据库的旧事,不想还有许多朋友认为有意思,希望多听点。所以我厚着脸皮,回忆了几个与数据库打交道的细节,记录在这里。

学校开数据库课程的时候,用的是高等教育出版社的教材。大概还有些朋友有印象,当时高教社出了一整套《面向21世纪课程教材》,正方形开本,压膜封面,依照专业不同,颜色分别是绿色、红色、蓝色等等。数据库的教材感觉中规中矩,就是从小到大都熟悉的模式。不过,当时有朋友介绍了一本新的教材,给了我很大影响。

这本《数据库:原理、性能与编程》也是高教社出的,当时国内刚开始有影印版的计算机教材,高教社也不甘人后,影印了《操作系统概念》在内的一大批经典教材,而且高教社的定价很良心,唯一问题是纸张太薄,但与价格比起来已经不算什么了。

An image to describe post 杂忆数据库旧事

那个年代“学英语”还是一个沉重的任务,更别说看英文教材了,所以几乎没有朋友交流,全靠自己啃。好在我前一年已经啃过一本C++的影印教材,并没有感觉特别困难。

这本书与“官方教材”最大的不同就是编排节奏,印象里开头很大篇幅都没有涉及数据库和SQL,反倒是和离散数学之类关系更近,作者还要不厌其烦告诉你:我说的是关系,不是表;我说的是元组,不是行;我说的是属性,不是列…… 当时觉得特别麻烦,表、行、列,多么清楚直观,看得见摸得着的东西,而关系、元组、属性都是抽象概念,理解起来麻烦很多,我们需要费那么大劲去绕远路吗?

而且还有大量的习题。计算机专业很多练习都是上机操作,但这本书开头的习题基本都只能在纸上演算,倒是和上机还很不方便的时代很般配。我记得有个题目我在自习室琢磨了一下午,绞尽脑汁也想不出来,最后无奈才看的答案。

题目简单复述是这样的(我使用了表、列、行等等直观说法):如果一张表中有若干行,除了一列的值不同,其它列的值完全相同,如何用SQL把这些列找出来?

如果用编程语言当然很容易,但用SQL就想破头也不知道怎么做了,尤其是在不借助其它临时表、变量的情况下怎么做,更是没有头绪。其实答案也很简单,这张表自己和自己做JOIN,条件是“其它列相同唯有一列不同”,就可以找出来。

大概是因为之前想破头也解不出来,看到答案却如此简单,这个题目我印象特别深刻。当时我瞬间就明白了,笛卡尔积原来可以“一口咬住自己的尾巴”。等到了工作中,也确实触类旁通,靠这个思路解决了不少诡异的问题。

在后来的面试中,我也曾经问过不少候选人这个问题,90%的人都答不上来。而剩下10%能答上来的,基本对数据库操作、数据建模都相当熟练。所以我经常在想:或许学数据库的时候不要一上来就SQL,而是从关系代数讲起,反而会学得更好?

如今数据库的教材丰富了很多,这本书的评价也算不上特别好,不过我时常还会想起它,感谢它。它也提示我:若你真的打算学一门知识,不要一开始就太过纠结教材、方法、老师。哪怕你的教材、方法、老师都不是最顶尖的,只要不是太离谱,都不是大问题,重要的是一门心思扎进去学,然后总会有收获。

我刚刚工作不到一个月,就遇上了封闭开发。

如今“封闭开发”似乎是个很久远的名词了,但是联系到大家叫苦连天的996、007,我忽然感觉“封闭开发”还挺幸福。通常的做法是把程序员拉到与世隔绝的度假村或者山庄,给好吃好喝伺候,每天早上9点干到晚上七八点,周末不休息或只休一天。如此看起来,以前的老板们似乎更加人性化一些。

那个时代的开发很简单粗暴,根本没有“敏捷”这一说,纯粹瀑布式,项目经理往下派任务,按期完成。编程语言是我在学校没怎么用过的Java,不但要用Java,还要用Java操作数据库——这时候我才知道还有个东西叫JDBC,因为之前只在RDBMS里写过简单的SQL而已。在这样巨大的落差面前,即便不怀疑人生,起码也要怀疑自己的大学:我之前学的那些方法是对的吗?做的那些题目都白搭了吗?……

这还不算,还有连接池、线程池、异常、单元测试等等一大堆新鲜概念扑面而来,让我这种在学校只写过“学生程序”的人目不暇接、捉襟见肘。原来,招聘启事上说的“提供丰富的成长和学习空间”,根本不是给你循序渐进、步步为营的机会,而是以铺天盖地、雷霆万钧的方式现身。

回想起来,那时候最大的煎熬不是白天,而是晚上。白天再怎么压力大,毕竟硬着头皮做就可以,晚上面对的却是激烈的思想斗争:这么难,明天要做的事情还没有一点头绪,要不要继续干下去?会不会被开?是不是明天就干脆不干回家更好点?再找工作的话,多久能找到,生活费从哪来?

每天晚上都在矛盾中辗转睡去,每天早上都没有下定决心,只能继续编程。

慢慢的,似乎从绝境中找到一点出路:不会的可以用Google去搜(那时候还可以直接访问),我英文还算可以,搜出来的东西基本能看懂,这样写程序无非慢一点,毕竟有进展;每找到一点新的想法都去和项目经理沟通(那时候的项目经理大多是技术出身,不只盯进度),听听他的意见;每次因为没做好被项目经理骂“不动脑筋”,都厚着脸皮不多顾及面子,赶紧改……

那时候每两三天都要开早会,每个人汇报自己的进展。有个同事是研究生,学历一直让我仰慕,结果在连续几次汇报“不会”导致任务延期之后,项目经理火了:“不会?什么叫不会?不会应该是你们说的理由吗?不会赶紧学!”

说完他还专门看了我一眼。那一瞬间我发现,与平时的“挑三拣四”、“吹毛求疵”不同,那是满意的眼神。

“不会不是理由,不会赶紧学”,这是我刚工作第一个月听到的。后来想起来,能这么早听到这样的话,也算是种幸运。

有趣的是,这次封闭开发完,技术总监考虑到大家在数据库方面能力的不足,专门从搜狐请了DBA来给大家培训(那还是三大门户的年代)。连续三个周末的培训下来,除了知道之前从没听过的“行迁移”等等问题,收获最大的是DBA给我们提供了他多年的精华:脚本包。日常的任务基本都能从里面找到对应的脚本,改改参数就可以用。从那以后,我也很注意收集整理自己的各种脚本,并且收获颇丰。

存储过程、触发器这种东西,在学校的时候确实接触过,但也只是“玩过”,真正该怎么用,我很长时间里一直都不知道。

刚工作那几年,听到做项目的朋友抱怨其他人把存储过程当编程语言来用:复杂的处理流程不用程序代码来写,而是用PL/SQL写成了三百多行的存储过程,使用的时候直接调用,然后就拿到结果了。至于其中的逻辑,许多人尝试去梳理,都无功而返。在这种设计里,速度慢还不是最大的问题,源代码管理、更改处理逻辑、处理能力复制才是更加令人生畏的噩梦。所以我得出了一个结论:存储过程、触发器这种东西,尽量少用。

大概是在2006年左右,一个问题让我希望尝试使用触发器。当时要做的是多张简单表的数据联动,没有想到用消息队列,只能以程序代码不断循环扫描各张表,找出变化量,再更新其它表。其实每次的数据变更都不大,但不断循环扫描的程序总是出各种问题,而且不方便调试。

考虑到数据变更的量很小,计算量也很小,我想触发器应当很合适。在测试环境写好之后验证,确实没有问题,不需要依赖外部程序,只要DBMS在跑着,内部的数据就会自动更新。看上去,再也没有比这更好的方案了。

第二天找了一个不忙的时间,神不知鬼不觉的,我就把触发器部署到生产环境,接着停掉外部程序,然后紧张观察,发现一切正常。我估摸着,下个礼拜的技术分享,我可算有的说了。

谁知道还不到5分钟,我的希望就化为了泡影。楼下陆续有人喊:数据库怎么不转了?谁把数据库锁住了? 我赶紧登上数据库,才发现已经有几百条一模一样的SQL语句都在等待执行,它们全都来自触发器……

后来我才知道,在做这个设计的时候,我只考虑了数据正确性和计算成本,没有考虑到系统压力。触发器所在的表每天写入超过一百万次,峰值每秒写入行数超过50(那是在2006年)。这种压力下,触发器确实很容易卡死。

还好我迅速就撤回了修改,谁也没有发现真正的原因,只是给自己留下了深刻的教训。

不过我认为,要想把技术练好,总要有些不甘现状,勇于“折腾”的念头,哪怕要因此吃点亏。也得亏有这次教训,我终于知道触发器这类东西要如何玩了。七八年之后一个偶然的机会,我用触发器设计了个“妖娆”的方案,解决了问题。

当时是帮朋友的忙,遇到的是个MySQL+PHP的遗留系统,原本设计容量大概只有几万的表,使用多年以后已经有了接近三百万的数据。这样一来,再用 select count + where 来进行各项统计就已经很吃力了。

可是这些数据统计偏生又是主管们关心,并且希望实时看到的。一开始是系统响应慢,显示不出来,结果越显示不出来就越刷新页面,越刷页面系统的压力就越大,如此陷入恶性循环。

如果有充分的时间和资源,当然可以把这个系统全面重构,彻底杜绝此类问题。但业务需求常常要和技术改造打架,难得有那么多的时间和资源。眼下这个问题,就只有一两天的时间。

在评估了数据的更新频率、统计数据的计算量之后,我设计了一张统计表,把需要统计的数据都存在表格里,同时写了多个触发器,按照源数据更新的方式不同,以不同的触发器去更新统计表里的统计数据。然后再把统计数据的取数逻辑稍作修改——只是改改SQL语句,对接到统计表而已。因为原有系统里,PHP没有任何“状态”也没有任何“后台程序”,相比搭建中间件,用触发器的思路虽然妖娆奇特,但确实成本低。

在仔细测试过之后上线,迅速解决了统计数据更新的问题,准确、及时,而且整个改进只用了不到半天时间。

如今时常有人问我架构怎么做,有什么独门秘籍。其实我想来想去,架构这东西真没那么神秘,大部分问题还是在时间、安全、稳定、性能等方面做权衡,同时注意控制复杂度。权衡需要宽阔的视野、良好的沟通能力、多样化的价值观,不能钻牛角尖,要能妥协而且懂得如何妥协;控制复杂度的很大一部分工作则是合理划分责任:事情还是那些事情,切成多少个部分,每个部分放在哪里,怎么实现,怎么组合。这工作有时候看起来像拼图一样简单,其实需要之前修炼的经验和眼光才能决策,而架构设计的水平高下,往往就存在于那些拼图方案之中。

如今有许多人总给我戴上“牛人”的帽子,这总是让我无比为难。第一世间高人很多,见过越多就越谦卑,万万不敢夜郎自大;第二我讲的很多东西很普通,甚至有很多如今看来很“愚蠢”的经历,“牛人”的人设反而会因为这些经历减分,两相比较,还是不要虚名才最安心。

An image to describe post 杂忆数据库旧事

如果您认为本文有意思,欢迎长按识别上面二维码订阅。

“余晟以为”虽是个人号,但只用心做原创,不虚张声势,不故弄玄虚,不带节奏,力求定期更新,只为和你一同探索世界,分享致中平和的观点。