在码农的世界里,一个表面上微不足道的改动,有时却会引发系统的连锁反应,就好比一个蝴蝶轻扇翅膀,可能引来一场风暴。大家好,我是小义,作为程序员,相信大家也遇到过不少生产问题,有的可能是惨痛的教训,有的却可能是成长的经验。今天,小义就来讲述之前遇见过的一次生产事故,只是一个简简单单的表字段添加,竟然差点造成服务器瘫痪?
按照惯例,先还原一下灾难现场。假设有这样一张user用户表,保存用户ID、姓名、地址等字段,生产环境现在里面有7百万的数据量。
字段名 | 数据类型 | 描述 | 是否为空 |
---|---|---|---|
UserID(主键) | BIGINT | 唯一标识符 | NOT NULL |
FirstName | VARCHAR(50) | 用户的名字 | NOT NULL |
LastName | VARCHAR(50) | 用户的姓氏 | NOT NULL |
BirthDate | DATE | 出生日期 | NULL |
Gender | CHAR(1) | 性别('M'或'F') | NULL |
Phone | VARCHAR(32) | 手机号 | NULL |
Address | VARCHAR(255) | 地址 | NULL |
...... |
本来系统已经正常上线运行了,但是在后续的需求迭代开发过程中,产品小A说用户需要和不同业务线组织绑上关系,一个组织对应多个用户,一个用户只能有一个组织,即一对多的关系,那么User表就需要加一个OrganizationID(组织表主键)字段进行关联。
于是开发小B就开始加字段,心想这不是很简单吗,一句sql就搞定了。
ALTER TABLE User
ADD COLUMN OrganizationID BIGINT(20) COMMENT '业务线组织ID';
OrganizationID
历史数据都是null,暂且不管它,优先对新用户存组织ID,即对之前的insert插入语句也多添加了OrganizationID。
看起来没什么问题, 在发版日晚上小B将脚本部署上线,就开开心心回家了。 小B心想自己都选在凌晨,系统使用率最低的时间段发布了,而且也检验过功能了,这次发版肯定十拿十稳,于是就呼呼大睡了。
第二天日,运维小C像往常一样监控着服务器的运行状态,突然,小B负责的服务告警信息不断跳出,CPU使用率飞速攀升至80%以上,服务器仿佛随时都有可能挂掉。这一幕,让小C感到震惊又困惑。一番排查发现是某条更新sql造成的数据库死锁,赶紧拉来小B兴师问罪。
小B一看那条阻塞sql,果然是自己写的,这下真是完蛋了。原来是产品小A后期改需求,小B就加了user表根据OrganizationID来更新某些字段的一条sql,可是自己在测试环境明明验证了一切正常啊,问题出现在哪呢?
update User
set xxx=xxx,update_date = now() where OrganizationID = xxx;
遇事不要慌,来杯82年的JAVA压压惊,小B开始静下心来,认真的想了想。服务部署上生产环境后,系统访问数量一下子就上来了,查看日志发现有很多执行上述sql的请求,而生产环境user表数据量本来就很大,足足有7百万,一看执行计划,没走索引,好家伙,这sql执行不慢才怪,小B都想扇自己一巴掌了。
找到了原因,就不慌了,赶紧先开启限流限制接口请求,然后让DBA临时给OrganizationID字段加上索引,过一会,数据库和服务器性能,就都恢复了正常,总算是把这一次的生产事故补救了回来。