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. 原因分析#
现在我建一个表来复现这个情况
代码语言:javascript复制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
更新语句正确的写法是:
代码语言:javascript复制update table set col = ?, col = ? where [条件]
3. 解决办法#
我们的解决办法是在阿里云下载故障发生前最新的备份,然后编写脚本,根据id一一对应的把state字段的数据修改过来,故障期间造成了一些数据的丢失,不过好在不多,只能根据他们的反馈然后把数据补上
最后,经过分析可以知道,开头提到的写法本身就是错误的,但是mysql并没有报语法错误,所以我运行完上面那个语句之后,结果线上出现了问题,一时之间还找不出究竟是什么引起的,隔了一段时间在同事的提醒下我才反应过来,如果不能及时知道问题的起因,那么排查问题的方向就会出错,拖得时间长了,造成的经济损失不可估量,幸好这个故障发生在国庆放假期间,不然真的就要跑路了