1.前言最近经常遇到开发者不小心误删、误更新数据。这不,他们又找我麻烦了。让我们来看看整个过程。2、流程因为开发需要修复生产环节的数据,需要执行120条SQL语句,需要更新数据。于是开发连接生产数据库,先执行第一个SQL。updatetablenamesetsource_name="bj1062-北京市朝阳区长影北辰府第"wheresource_name="-北京市朝阳区长影北辰府第"我们仔细看了一下,发现这条SQL确实没有问题,where条件也正常,大意就是在这个地址前面加上字符串bj1062,真的没有报错吗?是的,没有错误。开发执行完成后,结果确实符合预期。然后开发执行剩下的SQL,和上面的SQL一样,更新地址。执行完之后,开发一头雾水,发现source_name变成了0,开发赶紧给我打电话说:哈维,我执行了update,whereconditions是正确的,set的值也是正确的,但是之后set所有字段都变成0了,请速速帮我看看能不能恢复数据。赶紧上了服务器,期间查看了binlog,发现有大量updatetablenamesetsource_name=0语句,使用binlog2sql分析,项目地址:binlog2sql。赶紧开发,确定操作时间点,生成闪回SQL,进行数据恢复,同时保留现场证据。然后查看开发执行的SQL,发现有几个很奇怪的SQL:这些SQL的引号跑在where字段名后面,简化后的SQL变成:updatebl_namesetstr_col="xxx"="yyy"那么这个SQL是怎么执行的MySQL中的语义转换?会不会是这样的?updatebl_nameset(str_col="xxx")="yyy"语法错误,所以只会是下面的形式,updatebl_namesetstr_col=("xxx"="yyy")select"xxx"="yyy"的值为0,所以updatetbl_namesetstr_col="xxx"="yyy"等同于:updatetbl_namesetstr_col=0,导致所有source_name字段更新为0。我们研究下select形式的语句发生了什么。mysql[localhost]{msandbox}(test)>selectid,str_colfromtbl_namewherestr_col="xxx"="yyy";+----+-----+|id|str_col|+----+---------+|1|aaa||2|aaa||3|aaa||4|aaa|+----+--------+我们发现这条SQL也找到了str_col='aaa'的记录,为什么?mysql[localhost]{msandbox}(test)>warningsShowwarningsenabled.mysql[localhost]{msandbox}(test)>explainextendedselectid,str_colfromtbl_namewherestr_col="xxx"="yyy"\G*****************************1.row******************************id:1select_type:SIMPLEtable:tbl_nametype:indexpossible_keys:NULLkey:idx_strkey_len:33ref:NULLrows:4filtered:100.00Extra:Usingwhere;Usingindex1rowinset,1warning(0.00sec)Note(Code1003):/*select#1*/select`test`.`tbl_name`.`id`AS`id`,`test`.`tbl_name`.`str_col`AS`str_col`from`test`.`tbl_name`where((`test`.`tbl_name`.`str_col`='xxx')='yyy')这里他把where条件转化为:((`test`.`tbl_name`.`str_col`='xxx')='yyy')这个条件先判断str_col和'xxx'是否相等,如果相等,则里面括号的值为1,不相等则为0。然后0或1结合'yyy'进行判断。由于等号的一边是int,另一边是string,所以两边都转成float进行比较。你可以阅读我之前的文章。MySQL隐式转换导致查询结果错误的案例分析。'yyy'转为浮点型为0,0和0比较等于1mysql[localhost]{msandbox}(test)>select'yyy'+0.0;+-----------+|'yyy'+0.0|+------------+|0|+------------+1rowinset,1warning(0.00sec)mysql[localhost]{msandbox}(test)>select00=0;+-----+|00=0|+-----+|1|+-----+1rowinset(0.00sec)导致结果常量建立,即select语句等价于如下SQL:selectid,str_colfromtbl_namewhere11=1;将查询所有记录。3.总结在写SQL的过程中,一定要注意引号的位置是否正确。有时候引号的位置错了,SQL还是正常的,但是会导致所有的执行结果都不对。执行前必须在测试环境中执行测试,结合IDE的语法高亮可以发现相应的问题。
