最近经常遇到开发者误删、误更新数据。这不,他们又找我麻烦了。看一下整个过程,看得我好惨。图片来自Pexels进程。因为开发需要修复生产过程中的数据,需要执行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进行分析。项目地址:binlog2sqlhttps://github.com/danfengcao/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,所以:updatebl_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隐式转换导致查询结果错误案例分析:http://www.fordba.com/mysql-type-convert-analysis.html'yyy'转换为浮点型为0,0和0比较等于1------+|0|+------------+1rowinset,1warning(0.00sec)mysql[localhost]{msandbox}(test)>select0=0;+-----+|0=0|+-----+|1|+-----+1rowinset(0.00sec)导致结果不变,即select语句等价于如下SQL:selectid,str_colfromtbl_namewhere1=1;将查询所有记录。小结在写SQL的过程中,一定要注意引号的位置是否正确。有时候引号的位置错了,SQL还是正常的,但是会导致所有的执行结果都不对。执行前必须在测试环境中执行测试,结合IDE的语法高亮可以发现相应的问题。作者:Harvey编辑:陶家龙来源:www.fordba.com/mysql-double-quotation-marks-accident.html
