一个让人目瞪口呆的BUG

几乎快要忘记这个故事了。

我的记性就是这么不靠谱,总是要把发生的事情写下来才肯放心。所以,才能从代码库中找到这个项目。所幸的是,开源中国的私有代码托管太棒了。

事情是这样的,当时有一家儿科医院,在病人住院后,要每天检测一次患者的各项指标,评估其死亡概率。 从程序员的角度来看,就是输入一组数字,根据给定的公式,算出结果,这么简单。

不知道医生每天在计算这些数据时心里在想什么,毕竟,计算别人的死亡率,感觉不太好。专门负责这件工作的人,每天要输入上百份数据,然后手工汇总到Excel中,也不轻松。

他们提供了一个在线计算的网站,由于年代久远,网址早已失传,只找到了这张截图:

这是他们每天都在使用的工具。关键是操作十分不便。他们要打开病历,照着上面的数字输入,第一项结束后,要把手移动到鼠标上去切换到下一项。比如200份资料,要输入200次,15项,那就是3000次输入。而且由于网站不能保存数据,他们要把计算结果复制到自己的Excel中。

这个Excel是他们要的结果,对于非IT人士,用于统计和研究,Excel是最容易,最常见,可能也是唯一的数据格式了。

so,需求主要有三点:一是不能依赖网络(说不定哪天断网了,或者这个网站挂了),二是要尽量减少人工的劳动强度;三是要把数据保存到 Excel 中。

具体需求如下:

1 提供一个界面,上面显示各项指标和计算结果。

2 每一项指标参数,都有格式要求。比如,x.xx, 意思是小数点后两位。比如,当输入 1.23 以后,光标自动跳转到下一个输入框。以节省切换光标的操作。

3 打开界面后,光标自动位于第1个输入框。

4 有一些固定选择的参数需要做成下拉框,成为焦点(就是获得光标)后,要自动弹出全部选项,自动选中第一个,用户可以用上下箭头选择。用右箭头结束。

4 全部输入完成后,结果实时出现。

最后的运行效果,应该是,用户不用看屏幕,盲输,眼只需要看着病历,手只需要放在数字键盘上,这样效率最高。

这样一个简单的程序,居然也要经过13次修改,才能让客户满意,也许当年我比较菜吧。最后完工的界面如下:

然而交付以后,用户说,按下每一项输入完成后,根本都不能自动跳转。

这是个大问题,相当严重。

对方的机器是XP系统,所以,我装了XP虚拟机,反复测试,在我的机器上没毛病。

怎么都测不出来这个BUG。

我要求对方把硬盘摘下来邮寄给我,遭到拒绝,理由是硬盘里有医院的机密信息。

我找了一台旧的台式机,在上面装了XP,依然不能测出问题所在。

最后,我打算亲自跑一趟,去现场解决这个问题。想一想路费,有点犹豫。最后,我说,要不然,你装个录屏软件,让我看看你是怎么操作的。

录屏没毛病。

我开始疯狂地测试,无论如何都无法重现这个BUG,测到怀疑人生。

我说,不然,你用手机拍下你的操作,把手的运动和屏幕一起拍给我看。

这下,我找到了原因,原来对方用的是小键盘。而我是用笔记本开发的,没有小键盘。

问题就这么解决了。我的判断逻辑,只针对键盘上面的一行数字跳转,因为我的笔记本根本没有小键盘啊。

那么台式机是怎么回事?因为我从来不用数字小键盘。

顺利结束。

后来,我在跟别人讲到这个BUG时,两个人都笑出了眼泪。

再谈IT培训

再谈IT培训

曾经听说李笑来开班讲学,讲编程,收费六七万一期,人员暴满。

当年学校组织两个月的强化实习培训,有两个学分,但我没有参加。我觉得自己是学霸 ,没有这两个学分照样能毕业(就算不能毕业也不care,当年学霸的逻辑现在我不懂)。没有参加是因为培训在另外一个城市,免学费,但食宿自理。但我出不起食宿费,想留在这里挣这两个月的工资。(市场上三个月,我们只需要两个月,人人都是学霸)

其实我非常想去,亲眼见见牛人讲师是怎么编程的,但是身不由己啊。我已经在公司实习了半年,没有人教,全靠自学,非常的苦闷。

在此之前,我已经自学了N多年,不用避讳,其实基本是没学会什么东西。我曾经一度怀疑自己是不是这块料,但人必须找到一件活计来养活自己,编程是我能看到的唯一的活计。

我自学过很多门语言。自认为C语言学得还不错,可以在DOS下用字符画界面,类似于这样的:

 

我写的程序是仿照这个界面设计的,有菜单,有按钮,有边框,重要的是,可以在DOS操作系统中下运行,是真正8位程序。

现在的计算机都64位了吧,不一定能兼容8位程序。现在估计没有人用这种界面了,所以所有努力算是白费了。我是讲师的话,绝对不会教这种技术,相信所有的讲师都不会。

后来学C++,书上讲的一回事,现实中又是另一回事,一个最简单的操作,我要研究很久,经历好多次失败,不知道有多少次怀疑自己的智商。

多少次我在想,要是有人能教我该多好啊。我很后悔没有去参加学校组织的培训,还没毕业,有三分之一的同学去了北京,被有名的软件大厂定向招走了,起薪4K+,而当时的我在本地,已经实习了一年,连这一半都不到。

所以每次见到那些培训生,都怀着无比复杂的心情,一方面是嫉妒,一方面是后悔。然而后面的人生中第一次招聘,改变了我的看法。

不知道是学校格外开恩还是我天生学霸,没有培训实习的学分也拿到了毕业证。毕业证拿到手没几天,同学们各奔东西。校招,领导让我选一些人来面试。打开邮箱后,傻了。几百份简历,几乎都是用同一个模板写的,都是三个月培训,加公司实习半年,有三个项目的实际经验。编程语言,全都是JAVA。

这些简历,像是工厂里下来的标准件,几乎一模一样但又各不相同,真不知道该如何去选。看了几天,看麻木了,领导开始催我了。

我对JAVA一无所知,只好胡乱选了几个。

第一个是同校不同校区的男生,他在学校里给老师做过项目。带着笔记本过来面试,领导不在,让我接待下。我让他打开他做的项目,随便指着一个地方,说这段代码,你给我讲讲什么意思。
他讲不出来,我跟领导说他能力不行。

第二个,是女生。跟着学校那批去培训了。我觉得女生可能学习更认真吧,况且是同一个系的同级同学。
来了之后,没带笔记本,不能看项目,只好尬聊。
她说:“我不知道我喜欢什么,同学们都在投简历,所以我也投了。”
我心想,这是个没有规划的人。打发走了,跟领导汇报说,投错了简历来的。

第三个没有选我们学校的,故意选了个专科的。想着专科的学生可能动手能力强,不会天天死读书。然而我错了,打了三次电话,每次都约好时间,结果却每次都不来。

领导开始批评我,并且给我贴了个“不适合搞管理”的标签,从此我与管理岗位无缘。

领导就是领导,亲自出了一份笔试题,考智商的。跳过技术考核(是不是感觉都白培训了),两天面试了十几个人,最后选了两个。

一个笑起来露出八颗牙,上四颗,下四颗,像是要把嘴吸在玻璃上那样。我根本不可能选他面试。
另一个,也好不到哪去,笔试三分钟放弃答卷,交了白卷。

居然都进来了。
其实都是刚毕业,大家都是白纸一张,何必挑来捡去。

第一个实习三个月后,进了用友,薪资10K起。不是我带的好,是人家本来就牛。

第二个只待了一个月,因为他早就被某985学校录取读研了。华丽丽的默默逆袭。

都算是牛人。领导虽然不懂技术,选人那是真牛啊。

培训出来的不见得就比别人好,牛人培训了还是牛人,渣渣培训出来还是渣渣。问题大家都培训三个月,就你不培训,你都不好意思去投简历。

所以所有的简历上都是培训三个月。培训两个月的,除了我们学校可能绝无仅有吧。

所以我也想去培训三个月,然后跳槽去北上广拿高薪。

去培训机构一打听,培训费一般一万五起,高的两三万,我根本付不起。所以真的太后悔没有参加学校的免费培训了,亏大了。本来想挣点生活费,结果却亏了学费。

新人一走,办公室就剩我跟领导两个人,天天大眼瞪小眼。我天天自学,每天都学到怀疑人生。

怎么办?接着招呗。招了三年,前后没有几十个也有上百人了,多到我都记不起来他们的脸和名字了。有打酱油的路人甲乙丙,也有一些自带天赋开挂的,比如有一哥们熬了6个月,然后跑到深圳干了半年就深得新老板赏识,遵旨开了分公司,如今总资产上亿。还有一哥们,三个月后去当了IT培训讲师,收入暴增十倍。

就是没有一个留下来的。

一个都没有。干不来的觉得没意思就自己走了。干的不错的,要的工资太高公司不愿意给。

来的人都跑了,为啥我没有跑?因为我没有培训啊。大家都培训为什么你不培训,简历上没有培训怎么好意思投呢。
每年至少损失100K的工资。

听说学校培训的那批同学,春节前从北京回来了。就剩下两个,一个女生转行干客服了。另一哥们升了开发组长。其余的人,全都转行了。两个班,75个人,就我跟他两个人在撸码,直到现在。(据我所知,我们学校这一届至少为社会贡献了8个职业程序员,另外6个是法律,物理,数学系出来的,8个人里面,7个都没有培训,培训过的约300多人里,只有1个以编程为生)

另外那哥们,我还想多一句,是个奇葩,没有学籍,没有毕业证。所以学历算是高中。多年以后参加成人自考拿到了本科学历,那是后话了。

是这个行业竞争激烈吗?并不。那时互联网还没有这么火,编程是个没有未来的体力活,职业年龄不会超过30岁,接受传统教育的高智商人群不屑于这种雕虫小技。要么去搞研究发表论文,青史留名,要么不济也得混个中产吧。考研,出国,进500强,事业编制,才是正道。

竞争激烈的是开高薪的企业。高薪者,必要求高能力也。能力不够,还是白搭。

领导终于下定决心不再招应届生,并且开“高薪”,要求至少是五年经验。所以呼啦啦招了十几个进来。

而我刚好够五年经验了。不,我还实习了一年,是六年。

看,公司还是能开高薪的。之前没开,是认为不值得。包括我在内。

当年不愿意花一万五培训,结果跳不动槽,并且五年损失了六七十万,客观来说,可能是八十万,到一百万,的薪水。

真可惜。那时没有信用卡,在最美好的青春里,一贫如洗。

所以笑来老师的培训课,六七万,真的很值,要是我刚毕业,又不想进入“腐朽的系统”,会买。没钱,去借,去套,去撸小贷。为了一个未来,一切都值得。

贫穷,限制了我的想象力。

可见IT培训,是门好生意。只要还有人出身草根想改变命运,只要这类人还有远见,就永远会有市场。

圣杯布局

圣怀布局与双飞翼布局非常相似,实现同一种效果,只不过实现的方式有一点点区别。

两侧宽度固定,中间自适应,相当于三栏布局。

.container {
  padding: 0 250px 0 200px //将左栏向右挤,右栏向左挤 
}

.main {
  width: 100%;  //撑满,自适应宽度。
}

//浮动布局
.column {     
 float: left;
 height : 300px;
}

.left {
  width: 200px;
  background-color: pink;
   margin-lefg: -100%; // 向左移动整行的宽度,float 居左显示,盖到 main 左边。
   position:relative;
   left: -200px;  //左栏向左移动200:因被向右挤,再向左移回。
}

.right {
  width: 250px;
  background-color: orange;
  margin-left: -250px; //向左侧移动,盖到 main 右边;
  position:relative;
  right: -250;  // 右栏向右移动250:因被向左挤,再向右移动;
}



<div class="container">
  <div class="main">my name ia andy.</div>
  <div class="left"></div>
  <div class="right"></div>

</div>

代码解读:
1 main 左右撑开100%,自适应宽度;
2 left 与 right 浮动布局,被挤到第2行;
3 left 与 right 使用 margin-left 向左移动对应长度,盖在 main 上面;
4 container 父元素使用 padding 压缩布局空间,使空间左右留白;
5 left 与 right 使用 position + left/right 相对定位,分别移动到左右的空间里。

 

应用层事务引擎设计概述

 应用层事务,是在应用层保证对数据的操作能够正确完整地执行的机制。

应用场景:

1 数据库重启:
应用在执行数据库存储过程时,数据库重启,连接丢失,应用层得到数据库连接断开的回复,不知道此事务是否执行完成。

2 网络抖动
网络抖动时,也会得到数据库连接丢失。尽管以后数据库会恢复,但是应用层得到执行失败的结果,却不能处理。

假设应用的执行在服务器是瞬间完成的,要么成功,要么失败。称为“原子事务假设”。

然后再针对复杂事务,假设其执行时间不可忽略,在事务执行的过程中,出现故障,或是代码异常,或是数据库重启,网络中断,导致事务的一部分成功,另一部分失败。这种称为“复合事务假设”。

很显然,复合事务假设相当复杂,也不易模拟重现,而且,也不能排除它发生的可能性,尽管这种概率看起来并不大。

原子事务假设

对超过多条数据的更改,都封装在存储过程中执行,在存储过程中使用数据库事务,以确保其原子性。且在存储过程中返回结果代码,供应用层判断。

应用层可以根据操作代码执行结果是否正确。如果结果不正确,则应根据不同的原因做出相应的处理。

1 代码错误/数据库异常
此类错误由偶然的代码逻辑错误,或者引入了偶然的非法数据。这种情况,无论如何都不会执行成功,就需要放弃这个事务,同时记录日志,反馈到开发环节去改进。

2 连接/网络 异常
异常在连接成功打开之后发生。如果连接无法成功打开,说明异常已经发生,则不能继续执行。
如果异常在连接打开之后发生,应用层不会得到正确的返回,无论存储过程执行成功与否。此时应用层需要执行数据检查,然而由于连接已经不能打开,则检查也无从谈起。
此时的处理方案是,把此事务序列化存储,同时中止一切其他事务的操作,直到网络恢复为止。

由上面的分析,得到应用层事务的需求如下:

1 事务应能序列化存储;不光存储事务数据,还能存储它的状态:未执行;失败需重试;失败已放弃;未知需检查;

2 日志:将由代码错误引起的失败事务的详细信息,记录下来,以供分析之用。

3 事务执行者(事务引擎)是全局的,当连接异常发生时,引擎将全局状态标记为不可用;同时引擎停止执行一切事务。

4 事务引擎,具有等待与检测连接是否可用的功能;当连接可用时,引擎恢复运行。

5 引擎恢复后,对存储的事务内容进行检查,逐条或并行执行。对于那些标记为“未知需检查”的事务,执行数据检查,如果检查通过则说明事务执行成功;否则重新执行此事务。

复合事务假设

复合事务,从表面上看来,也是一种原子事务,但它的内部,可能由多个其他的原子事务组合而成。

假设某复合事务 X ,由原子事务 A, B 组成,两者是顺序执行。

在 A 异常后,事务引擎会停止,同时检查A执行是否成功,如果 A 失败,则X需重新执行。 如果 X 成功,则应把B当成另一个紧急事务加入队列,等网络恢复后优先执行。

如果A的结果无法确认,则将 复合事务 X 标记为“需检查”,同时,检查点设置为A。这样,当网络恢复时,引擎会检查A的执行结果,再决定 如何 处理X。

如果 B 发生了代码异常,那么A的成功操作应撤销,然后放弃 X 的执行。

由上面分析,得到以下需求/结论:

1 复合事务,是原子事务的子类。

2 引擎能够区分复合事务与原子事务的区别。可以使用“状态模式”来实现多态。

3 引擎的内部队列,有优先级,可能不止有一个队列。

4 复合事务的组成者,要具备 在成功操作后 撤销的功能,这就引出了 “反向事务”。 如果 反事务也失败了呢? 引擎的设计可能 更麻烦了 ,头大, 暂时放弃这个吧。

其他问题

1 事务队列应持久化到磁盘上,或由第三方消息队列来管理。防止 应用崩溃后队列中的数据丢失。

2 实时性要求:事务引擎的多线程及同步问题。

3 目前的ORM并不支持事务,这是一种不够安全的做法。考虑到与现有系统的兼容,事务最好能做成 ORM 框架的一个扩展实现 。因此,事务引擎应是最低层接口,其上层有ORM封装和应用封装。

MySql并发读取性能测试

评价:MySql 的并发读取性能受到带宽与CPU制约影响比较大。平均每m可以支持100个并发连接,且处理速度为10个每m。随着并发连接数增多,延迟增加,处理速度降低,这些连接会保持,消耗服务器资源。

建议: 架构设计时,应考虑:有大量并发需求时,最好避免跨网络连接数据库;应用与数据库最好位于同一内网。

背景概要:使用办公内网,连接到生产内网的数据库服务器,中间使用ngrok进行数据转发,也可直接通过动态域名连接。
测试目标:搜集收下几种场景下的MySql连接响应时间,得到最大并发经验数据。
测试指标:分别测试1,10,100,500,1000,3000,这些并发连接的:平均读取完成时间;总任务时间,并计算每秒处理并发数量。
测试方法:使用命令行程序进行连接,并输出测试数据到文本文件。
测试场景
1 本机MySql。
2 本机与虚拟机MySql。
3 局域网MySql。
4 跨网络MySql(使用动态域名直接连接,与使用ngrok中转服务器连接)。

测试结论

场景 最大安全并发数 最大并发时响应时间 最大并发时每秒处理连接数 CPU占用 峰值带宽

(mbps)

带宽总量 结论
本机 >10000 <20ms 2000~2500 100% 2000~2500 是单机并发上限,受CPU限制。
虚拟机 >10000 约20ms 1500~2000 100% 虚拟机的并发上限是2000,受CPU限制。
内网跨机 >10000 <100ms 1400~1700 50~80 100m 生产库的并发上限受制于带宽,也可能与CPU有关。
外网直连 8000~1000 <2s 800~1000 30~40 100m 外网通过动态域名直连并发数受限于带宽,100M带宽的实际上传速度约为30m
服务器中转 80~100 2~3s 7~10 0.8~1.2 1m 带宽与稳定性,是一个矛盾,两者不可兼得。

模块化编程概述

本文讨论复杂应用场景下,应用层的水平模块化设计思路,也即桌面客户端应用层的模块化开发方法。
服务器,比如WebService服务器,从架构上来说相比于客户端,处于系统的更底层,如果负载压力不大WebService可以设计得很简单,单服务器就解决。而如果负载压力比较大的话,则需要在服务器集群中设计负载均衡方案,不过这属于另外一个话题了。
在具有许多关联业务需求的场景下,我们把这个场景叫做“应用上下文”。用户需要使用很多界面来解决他们的需求,这些需求之间具有一定的相关性,需要共享某些数据,在操作的过程中经常切换。在构建方面,不同的应用会共用某些基础设施,比如跟同一服务器通信,或者使用相同或相似的接口来访问不同的服务器,这时的操作逻辑会相当复杂,如果不够小心的话,一个错误的决定,会让开发过程会变得混乱,造成时间和成本方面的损失。
所以,当软件的规模越来越大,应用逻辑越来越复杂,为了简轻代码的维护负担,简化开发流程,模块化开发方式就成为一种必然。
截至目前本文作者已经全职工作了五年,专业主要集中在CS架构方面,因此对桌面客户端的开发较为熟悉。五年并不算太长的时间,所以不敢保证我的理解是正确的,欢迎一起交流探讨。 模块化解决方案有很多种思路,比如:

应用内模块化

在应用开发时将不同的功能代码放在不同的目录中,使之自然形成应用模块。在用户看来,这些功能是一个整体,并没有体现模块化的思想。然而这毕竟是一种不错的开始,现在用这种方法的人已经不多了,因为它的代码组织形式不利于多人同时维护,只适合于单人开发。 也许你要说,怎么就不能多人维护了,我用Git不行吗?Git本来就是用来维护代码版本的。这样说当然也可以,在笔者看来,开源软件经常存在多人同时编译同一份代码文件的情况,虽然会有合并冲突,然而这都不是事儿,只要足够的小心,抱着开放的心态,问题总能够解决。但在生产环境之下,赶进度才是大事,每人只想管好自己那一块,别人的东西不想插手,插手也不合适,那是别人的工作边界,因此多人共同编辑并不必要,最多也就是浏览下别人的代码。
类库模块化
不同的模块开发成对应的类库工程,然后分别编译,或者与主程序一起编译,相当于把应用内模块化的功能目录从主工程中拿出来了,这又是一个进步,但是这种方法仅仅是提供了一种分布式开发的可能性,然而类库的调试还要依赖从主工程启动,因此在工程组织上,它与应用内模块化相差不大,同样属于一种比较原始的方法。

进程隔离模块化

把不同的功能开发成不同的进程,在进程间使用管道,共享内存,或者本地端口来共享数据。主进程可以直接向操作系统发送程序启动命令来启动另一个模块,这种开发方式的好处是进程隔离使得不同模块之间具有一定的安全性:如果某个模块崩溃,则其他的模块仍然可以正常工作。意味着不同的模块可以由不同的人或团队独立开发与测试,然而这仍然存在着一种可能性,那就是通信接口的调试要依赖于另一个模块,如果对方处于运行中,才可以得到回复。不过可以使用对方模块的一个原型,或者自己写一个Mock来替代,然而这需要建立一个完整的可运行工程,成本稍微有点高了。

静态插件式模块化

插件式模块化是真正的模块化,其基本原理是利用反射技术来加载实现了特定接口的模块,并调用此接口所实现的方法来访问插件所实现的功能。之所以叫做静态插件,是因为主程序在启动时检测与加载插件,当主程序启动完毕,所有的插件都被加载到内存中等待调用,它的内存代码是静态的。在代码组织方面,主工程暴露了一些必要的全局接口,像菜单,配置文件,工作区信息等,这样插件可以使用这些全局接口来访问主工程的一些功能,从而实现交互。不同的插件之间,也可以通过主工程的通信接口来通信。相比于进程隔离模块化来说,插件的调试依赖于主工程提供的接口,而不依赖于其他的插件,这就实现了插件之间的相互隔离。如果一个插件依赖于另一个插件的插口,可以简单地Mock一个接口而不是Mock一个进程,调试起来就更加方便了。 静态插件的应用十分广泛,比如为知笔记,再比如著名的Visual Studio提供的插件叫做“扩展”,也是这种静态方式。还有eclipse这款开源界的明星产品也是如此。

动态插件式模块化

动态插件与静态插件的不同之处在于,在安装了插件之后就可以使用插件提供的功能,而不必重启主程序。这是由于主程序把插件的代码动态加载到内存中执行,因此我把它叫做“动态插件”。 比如NotePad++是一款常用的文本编译器,相信读者一定不对对它感到陌生。它提供了一个插件库来扩展,就是这种方法。
接下来,我将单独写一篇文章来详细介绍插件式模块化的原理以及实现方法。

子表被级联删除时触发器失效

先说结论:如题,子表被级联删除时触发器失效。详细描述如下:

在MySql(5.7.18)中,如果一个表t2中的某列c2为外键,关联了另一表 t1中的列c1,外链关系为级联,则如果t1中某记录删除,会级联删除t2表中相关数据,但是如果 t2表有触发器,则不会触发。这里,t1是父表,t2是子表。

这可能是MySql的Bug,也有可能是MySql的一种机制,不管怎么说,都是个比较简单的问题,设计数据库时需要注意。

血泪教训暂不细表,只说测试过程如下:

先建父表t1,具有两列:c1, c2,其中,c1为主键,

  1. CREATE TABLE `t1` (
  2.   `c1` int(11) NOT NULL,
  3.   `c2` varchar(45) DEFAULT NULL,
  4.   PRIMARY KEY (`c1`)
  5. ) ENGINE=InnoDB DEFAULT CHARSET=latin1

创建子表t2,也有两列:c1是主键,c2是外键,引用了t1.c1:

  1. CREATE TABLE `t2` (
  2.   `c1` int(11) NOT NULL,
  3.   `c2` int(11) NOT NULL,
  4.   PRIMARY KEY (`c2`,`c1`),
  5.   KEY `fk_t12_idx` (`c1`),
  6.   CONSTRAINT `fk_t12` FOREIGN KEY (`c2`) REFERENCES `t1` (`c1`) ON DELETE CASCADE ON UPDATE NO ACTION
  7. )

注意,这里的外键有 ON DELETE CASCADE 约束。

创建日志表:

  1. CREATE TABLE `t2log` (
  2.   `time` datetime DEFAULT CURRENT_TIMESTAMP,
  3.   `content` varchar(225) DEFAULT NULL
  4. )

日志表存在的作用是,当t2数据被删除时,在触发器中向日志表中插入记录,就知道触发器生效了。

  1. DELIMITER $$
  2. CREATE TRIGGER `Trigger_SubstationBox_Deleted` AFTER DELETE ON `t2` FOR EACH ROW
  3. BEGIN
  4.     insert into t2log (content) values (CONCAT(OLD.c1, OLD.c2));
  5. END;
  6. $$ DELIMITER ;

以上就是 脚本清单,插入数据 如下:

现在,来删除t2表中的任意一条数据,比如(2,6):DELETE FROM t2 WHERE c1 = 2 AND c2 = 6;

第二行,就是本次删除数据插入的日志,证明触发器有效。

然后,删除t1表中的数据 c1 = 2,可以预见,t2表中的数据 (1,2)(2,2)都会删除,但是会不会在日志表 中留下记录呢?结论是不会。

再看日志表,仍与上次一样:

实际上,第一次遇到这种事情之后,我并没有太在意,因为线上项目很复杂,偶尔有不可知因素丢失一两条数据,或者,删除时没有删干净,都是低概率事件,一般都不可重复。但是当用户反复提交同一个错误时,我意识到这下麻烦大了。

最后在测试环境中重现了错误,解决方案是:在父表AFTER DELETE时,遍历一遍子表数据,执行子表的AFTER DELETE触发器逻辑。

 

 

 

什么?你买了房了?在哪?多少钱一平?什么时候买的?一月还多少?

我有一个室友。嗯,我又要开始讲故事了。

半年以前,我搬到一间房子的隔断里。不要问我为什么住隔断,这不是重点,谁问我咬谁。

住隔断其实也很好,因为没有公共区域,大家都待在自己的卧室里扣手机玩电脑,出了门都是行色匆匆,一脸严肃,相互点个头就完了。卫生间与厨房也是轮流使用,你在用的时候我绝不进去。

从上月开始,有间房换了个室友,此君身材发福,脸庞白净无须,满脸笑容,眼睛经常眯成一道缝,颇有几分喜感。

我留意到他主动清扫过道两次,经常主动清理厨房的垃圾,加上他喜感的容貌,觉得这是个比较有意思的人。后来在做饭时,有一搭没一搭地聊了起来。其实我是比较内向的人,不喜欢主动聊天,就算是主动打招呼,点个头,笑一下什么的,就已经很为难我了。从菜价开始,到工作,到房价,周边的情况什么的。

比如:

“今天做的啥饭?”   “随便炖点青菜。”

“还有肉啊,生活真好,什么肉?”  “鸡胸肉。”

“鸡胸肉多少钱一斤?哪买的?”  “超市,不知道多少钱。我买菜不看钱。”

“鸡胸肉有营养吧。” “嗯,蛋白质含量高。”

“你吃这么多啊,能消化得了不?”  “我饭量大。”

然后我不再说话,把切完的菜收拾起来,腾出案板,躲进卧室。

“哎哎哎,你怎么不做了?”   “不饿了。”

再比如:

“你今天去哪了?”   “出去了。”

“加班?还是出去玩了?”   此时我长了记性,开始反问:“你今天出去了没有?”

“我就呆在屋里,哪也没去。”  “怎么不出去玩呢?” 问出这个问题时,我开始得意,这叫以其人之道还治其人之身。

“外面热啊。”    我突然意识到当开始用嘴提问来代替思考时,会拉低自己的智商:胖子怕热,这是一个基本常识。    我沮丧地回复:“哦。”

“诶,你今天去哪了?”      我一口老血差点没喷出来,哥们你还掂记着这个问题啊。“我就是出去转了转,也没去哪。”   我要是说去健身房了,是不是又得问我两百个问题才肯罢休?

“出去玩吧,就去远点。比如说去海边。”    “穷。”

“不会吧,你一月工资多少钱?”  “没多少。”

(此处省略转移话题两百句)

“到底多少?有没有一万?”   一股无名怒火开始上升,我告诉自己要忍住,开始面带微笑:“问这个问题没有意义吧,我要还房贷,还首付贷,还信用卡,京东白条,淘宝蚂蚁,房租水电话费开支,哪个月不得七八千块钱?”

“什么?你买了房了?在哪?多少钱一平?什么时候买的?一月还多少?”

我再也不聊了,拨腿就跑,躲回自己的卧室,也不管锅里的菜还架在火上。我把头蒙在被子里,压低声音咒道:“我出去干什么关你毛线啊,非要问东问西,把老子在郊区买的房子给问出来。好好的打个招呼当个好室友不好吗?操。”

同样生活在大城市,有人月薪过万年终奖比工资还多,而我穷到只能买郊区的房子,有必要揭这个伤疤吗? 如果我问比我早四年毕业的你,也有三十好几了吧,同是干IT的,到现在还没有买到房子,没有对象,没结过婚,可能都没有跟女人上过床,我问你心里痛不痛?谁还没有个痛苦,藏在心里就好了,你干嘛要挖这么深呢?如果我把你这些东西挖出来,让你亲口说出来,Are you happy??? (此处附带一张狰狞的脸)

再比如:

“你买的这是啥?”    “压缩饼干。”

“啥?压缩饼干?”  “嗯。”

“是压缩饼干?好吃不?”   “嗯。”

“多少钱一包?有没有十来块?”   “还行。两三块钱一包。”

“两三块?是不是不能干吃,得就着水吃那种?”        我:吐血而卒。

愤怒归愤怒,该反思还是要反思的。遇到这种人,你该怎么办?

这种模式,是不带脑子的连机关枪一样的发问,如果你没有警戒心,认为对方在跟你套近乎,就会把该说的不该说的全都招出来,事后后悔不已,恨不得抽自己几个嘴巴子。

城市这么大,物价这么高,生存如此不易,谁TM心里还没有点小秘密。那些想跟你套近乎的人,统统应该拉出去枪毙三天,枪毙一百次,枪毙一万次。

城市这么大,你总会遇到一些用嘴思考的人。我并没有很介意让你知道,只是不想亲口讲出来而已。你不会自己用脑子想啊,你又不是刚毕业的雏鸟,难道你没有被骗过,没有经历过几次失败,没有打翻过友谊的小船?

难道你不知道,没有不劳而获的事情,想知道别人一条信息,至少要先主动招供自己的一条信息?

话又说回来,遇到这种人,到底应该如何应对?

首先,要用开头两三句话,判断其模式,是否属于用嘴思考。

如果判断属实,要坚决以其人之道还治其人之身,

比如,从对方第三次问开始,就要在心里建立“你有完没完,还能不能愉快地聊天”的警惕心,坚决不再回答任何问题,而是以同样的问题来反问对方。如果对方回答了,就再问一句,双方扯平,然后想办法终止聊天。

如果对方在终止时没有明白意图,仍然要继续问问题,你就开启Hard模式,他问你一个,你就开始问他两个,甚至更多。

比如:

“你一月多少工资?”       “你呢?你多少?有没有十三薪?”

“不多,XXXX元吧。(或者其他回答)”   “有没有五险一金?年终奖多少?”

“五险一金都有,年终没有/年终XXXX元。”   “那你们福利怎么样?一年旅游几次?中秋端午发购物券吗?”

对方卒。     你赢了。

赢的感觉就是让他想抽他自己嘴巴子,让他两股战战,拨腿就跑,再见到你如同见到判官。

 

 

 

 

 

 

 

 

 

 

 

 

TO BE, or NOT TO BE,其实是个伪命题

我有一个朋友,嗯,好像80%的鸡汤都是以这句话开场的,那么我也来鸡汤一回吧。话说,几年前,有个朋友在电信部门做4G业务,突然有一天打电话问我:“我已经有连续几个月的业绩不是很好了,只能拿到基本工资,还要不要继续坚持下去了?”

我反问道:“你觉得这份工作有没有前景?” 小A说,“现在4G刚刚兴起,未来肯定会有前景的。” 后来我便没有再回答对方,也许答案已经很明显了。

这是几年前的事情了,后来小A坚持了几个月,业绩始终不见起色,便转行了。我们也没有再联系,不知道在新的行业做得如何。如果说刚毕业时很少有人不迷茫,可是工作了几年难道就不迷茫了吗?

小B是一个程序员,工作几年后,觉得自己情商太低,于是问一位很有学问的老师,说,“我好迷茫啊。”其实这句话的意思是:“我有个问题,不知该问不该问。”其实他想问的是,要不要从现在的研发岗位转到技术支持岗位,新的工作可能会更锻炼情商,也会让他见到更多的世面,综合素质也许会有所提升。

这位老师想都没有想:“我也很迷茫啊。”小B一时语塞,大家哈哈一笑,这事就这么过去了。

小B 的问题,其实答案也很明显,就像是《外科风云》里面那个漫画家医生楚珺一样,一边是漫画,一边是医学,在面对诱惑时,她也曾动摇过,数次打电话给庄恕,她的指导老师,但每一次都因为吞吞吐吐而没有说出口,最后她发现其实自己更希望成为一名医生。

前两个事件,都是真实发生的,而第三件事,则是电视剧里的情节,结果都是类似的:提问者其实心中已经有了答案,只是不能肯定,需要别人帮自己确定一下。

生活中的重要决定,其实大部分都在当时的情况下,内心早就已经有了答案。比如小A,理智的选择就是,再坚持一下,看看自己的极限会不会在市场暴发之后到达,最后发现其实自己并不适合做这一行。而小B的选择,其实也早就决定好了:他是如此热爱编程,让他放弃写代码,还不如杀了他呢。而楚珺也是一位非常用功的实习医生,也许没有庄恕的人格力量,她早就坚持不下去了吧。而她能留下来继续当医生,也是因为有庄恕职业道德的示范作用,让她看到了医生原来是如此伟大的一份工作。

以上是鸡汤部分。不是反对鸡汤,关键是鸡汤要有勺子。

当你有了 To Be, or Not To Be 的提问时,不妨从以下角度展开分析:

1   你真的热爱这份工作吗? 

如果你热爱它,那么就能够克服外在的阻力,去做真正的自己。至少也要尝试着去克服一下,再回来思考。

2  你对未来可能的状态是否有信心?

这里的未来,是市场的发展,以及自己的发展。如果你确信未来会更好,那么,现在的付出,其实就是一种投资。其实大部分人对于工作,是无所谓热爱不热爱的,比如很多人,是因为听说写程序工资比较高,才选择了这个行业。已经工作了几年的,也会因为不满意现在的收入而打算转行写程序,这本无可厚非,关键是要有信心。

而很多人转行程序员失败,原因就是对自己信心不足。刚入门时遇到各种挫折,觉得自己不是干这一行的料,放弃吧,已经学了好几个月,甚至可能交过了一笔高昂的学费,觉得不甘心。那么合理的选择,就是冷静地问问:我对自己在编程方面的学习能力,是否有信心?根据观察,那些抱怨编程太难的人,基本上都最后都放弃了。

3 哪一种生活,才是我想要的?

TO BE THIS,or TO BE THAT,其实都无所谓。意义,别跟我谈什么生活的意义。有个段子,说有个白领,向往农村的生活,于是辞去工作,带着行李去农村过他的田园生活了。然而没过久又回到城市来苦逼地打工,别人问他为什么,此君答:断电,断网,什么都可以忍,唯独蚊子不能忍。

不妨想一想,原本就生活在农村的人怎么能忍下蚊子的呢?肯定有各种方法,比如点蚊香,搭蚊帐,喷杀虫药, 但为何这个城里人就不能忍呢?段子归段子,可能他不能忍的恐怕不是蚊子,而是离开城市的各种不便吧。

所以,哪一种生活,才是你想要的呢?这就是你的答案了。

所以,TO BE, or NOT TO BE,谁的人生不迷茫,哪有这么多问题,根本用不着这么纠结,答案就在你心中。