1. 背景

最近有一个需求,需要根据业务需求更新数据库中某张表的state字段数据,这其实是一个很简单的需求,sql语句就一行更新语句:update table set state = ? where sn = ?,表示对table表根据不同sn去修改state的值,state是tinyint类型,sn是varchar类型且sn是唯一的,然后for循环这个操作。

当时我犯的错误非常低级,我写成了update table set state = ? and sn = ?,想当然的把where写成了and,然后还没有发现问题,最终执行的结果就是整张表的state的值变成了0,这张表一共有5千多万条数据,造成了一段时间的锁表,导致线上停了一个多小时,直到服务器报警我们才收到通知。

2. 原因分析

现在我建一个表来复现这个情况

CREATE TABLE `testupdate` (
  `id` int(11) NOT NULL,
  `state` tinyint(4) NOT NULL DEFAULT '0',
  `sn` varchar(64) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `sn_UNIQUE` (`sn`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

插入一些数据

接下来我执行这个语句update testupdate set state = 10 and sn = "aaa";,得到的结果如下

为什么会得到这个结果呢?其实是state = 之后的语句做了逻辑运算,首先看第一行,为什么这一行的state的值会变成1呢?因为10 and sn = "aaa"运算得出的结果为true,true转化成int类型为1,所以结果就是1;而后面的5行因为sn对不上,所以都为false,即值为0

更新语句正确的写法是:

update table set col = ?, col = ? where [条件]

3. 解决办法

我们的解决办法是在阿里云下载故障发生前最新的备份,然后编写脚本,根据id一一对应的把state字段的数据修改过来,故障期间造成了一些数据的丢失,不过好在不多,只能根据他们的反馈然后把数据补上

最后,经过分析可以知道,开头提到的写法本身就是错误的,但是mysql并没有报语法错误,所以我运行完上面那个语句之后,结果线上出现了问题,一时之间还找不出究竟是什么引起的,隔了一段时间在同事的提醒下我才反应过来,如果不能及时知道问题的起因,那么排查问题的方向就会出错,拖得时间长了,造成的经济损失不可估量,幸好这个故障发生在国庆放假期间,不然真的就要跑路了😂

我的反省是要更加的细心,我本身是知道更新语句怎么写的,但是有时候脑子一热就会写错,而且我是在国庆期间写的,没有做到全神贯注