当前位置: 首页 > 科技观察

MySQL双引号错位引发的血案

时间:2023-03-13 21:43:39 科技观察

1.前言最近经常遇到开发者不小心误删、误更新数据。这不,他们又找我麻烦了。让我们来看看整个过程。2.流程因为开发需要修复生产环节的数据,需要执行120条SQL语句,需要更新数据,所以开发连接生产数据库,第一条SQLupdatetabletablenamesetsource_name="bj1062-北京市朝阳区常营先执行北辰府第“wheresource_name="-北京市朝阳区常营北辰府第”我们仔细看了一下,发现这条SQL没有任何问题,where条件也正常。大意是在这个地址前面加上bj1062,也就是真的没有报错吗?是的,没有错误。开发执行完成后,结果确实符合预期。然后开发执行剩下的SQL,和上面的SQL一样,更新地址。执行完之后,开发一头雾水,发现source_name变成了0,开发赶紧打电话给我说:哈维,我执行了update,where条件是对的,set的值也是对的,但是设置后的值所有字段都变成0了,请速速帮我看看能不能恢复数据。赶紧上了服务器,这期间查看了binlog,发现有大量updatetablenamesetsource_name=0语句,使用mysql2binlog进行分析。项目地址:https://github.com/danfengcao/binlog2sql抓紧开发确定运行时间点,生成闪回SQL,进行数据恢复。然后把开发执行的SQL拿来查看,发现有几个很奇怪的SQL:这些SQL的引号跑在where字段名后面,简化后的SQL变成:updatetabletbl_namesetstr_col="xxx"="yyy"那怎么办这个SQL在MySQL中执行语义转换吗?会不会像下面这样?updatetabletbl_nameset(str_col="xxx")="yyy"语法错误,所以只会是下面的形式,updatetabletbl_namesetstr_col=("xxx"="yyy")和select"xxx"="yyy"的值是0,所以原来所有的结果记录都变成了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";+----+-----------+------------+--------+--------------+--------+--------+--------+------+----------+----------------------+|id|select_type|table|type|possible_keys|key|key_len|ref|rows|filtered|Extra|+----+------------+------------+--------+--------------+--------+---------+--------+------+----------+------------------------+|1|SIMPLE|tbl_name|index|NULL|idx_str|33|NULL|4|100.00|Usingwhere;Usingindex|+----+------------+----------+--------+----------------+--------+---------+------+------+----------+-------------------------+1rowinset,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,再为'yyy'做出判断。由于等号的一边是int,另一边是string,所以两边都转成float进行比较。可以看我之前的文章MySQL隐式转换导致查询结果错误案例分析mysql[localhost]{msandbox}(test)>select'yyy'+0.0;+------------+|'yyy'+0.0|+------------+|0|+-----------+1rowinset,1warning(0.00sec)mysql[localhost]{msandbox}(test)>select0=0;+-----+|0=0|+-----+|1|+-----+1rowinset(0.00sec)

这样就导致常量成立结果的,即select语句等同于下面的SQL
selectid,str_colfromtbl_namewhere1=1;所有的记录都会被查询到3.总结在写SQL的过程中,一定要注意引号的位置是否正确。有时候引号的位置错了,SQL还是正常的,但是会导致所有的执行结果都不对。执行前必须在测试环境中执行测试,结合IDE的语法高亮可以发现相应的问题。