背景
程序(php写)中的一个变量传入mysql的where条件查询;发现变量改变查询的结果不变。
解析sql原生语句:
1 | select * from `flow_data_logs` where `msgid` = 258041935053193246 limit 1; |
1 | 的结果 msgid 不是 258041935053193246 |
改写sql:
1 | select * from `flow_data_logs` where `msgid` = '258041935053193246' limit 1; |
1 | 的结果 msgid 是 258041935053193246 |
心路历程
mysql 隐式转换和php弱类型带来多次坑,但是每次依旧采的很欢乐。
本次查询耗时接近两个小时复盘如下:
- 发现问题,想办法去定位
- 先pull代码重启进程,发现无效
- 加断点日志,添加参数和pid,发现数据是先被读出来然后才能写进去,而且是同一个进程,懵逼了
- 排除代码有没有在另外机器上执行
- 仔细查看日志发现查出的msgid和传入的msgid不是一条数据
- 定位到问题,担心是不是超出integer范围溢出了,查询之后排除
- 传入参数强制转换成string,问题解决;查bug两小时解决bug两秒钟。
也说php弱类型语言
PHP 弱类型语言变量在定义中不需要明确的类型定义,变量类型是根据使用变量的使用变量的上下文决定,也就是说,如果把一个 string 值赋给变量 $var,$var 就成了一个 string。如果又把一个integer 赋给 $var,那它就成了一个integer
1、php中的整型
- 整型数的字长和平台有关,且 PHP 不支持无符号的 integer
- 最大值常量 PHP_INT_MAX ,最小值常量 PHP_INT_MIN (需PHP大于 7.0.0)表示
- 32位平台下 最大 2147483647 64位平台最大9223372036854775807
- 如果给定的数超出了integer 的范围,将会被解释为 float
示例64位系统下的常量
1 | ❯ php -r "echo PHP_INT_MAX;" |
示例 64位系统下的整数溢出
1 | ❯ php -r "var_dump(9223372036854775808);" |
2、php类型转换
php 中类型强制转换:在要转换的变量之前加上用括号括起来的目标类型
- (int), (integer) - 转换为整形 integer
- (bool), (boolean) - 转换为布尔类型 boolean
- (float), (double), (real) - 转换为浮点型 float
- (string) - 转换为字符串 string
- (array) - 转换为数组 array
- (object) - 转换为对象 object
- (unset) - 转换为 NULL
【 tips】 (unset) 转换在 PHP 7.2.0 中已被废弃。请注意 (unset) 转换等于将值赋予 NULL。(unset) 转换将在 PHP 8.0.0 中被移除
再说 MySQL 的隐式转换
1、MySQL 中对隐式转换的定义:
When an operator is used with operands of different types, type conversion occurs to make the operands compatible. Some conversions occur implicitly.
当操作符与不同类型的操作数一起使用时,会发生类型转换以使操作数兼容。
2、比较操作时 MySQL 隐式类型转换规则
- 两个参数至少有一个是 NULL 时,比较的结果也是 NULL,例外是使用 <=> 对两个 NULL 做比较时会返回 1,这两种情况都不需要做类型转换
- 两个参数都是字符串,会按照字符串来比较,不做类型转换
- 两个参数都是整数,按照整数来比较,不做类型转换
- 十六进制的值和非数字做比较时,会被当做二进制串
- 有一个参数是 TIMESTAMP 或 DATETIME,并且另外一个参数是常量,常量会被转换为 timestamp
- 有一个参数是 decimal 类型,如果另外一个参数是 decimal 或者整数,会将整数转换为 decimal 后进行比较,如果另外一个参数是浮点数,则会把 decimal 转换为浮点数进行比较
- 所有其他情况下,两个参数都会被转换为浮点数再进行比较
示例int 转换成浮点数 sql
1 | SELECT '258041935053193233' = 258041935053193246; |
3、参考文献:
回归问题本质
- php 拿到值为整数的$msgid变量,变量类型为int且没有溢出
- php 把int类型的 $msgid传给myslq作为 where msgid=$msgid 条件(myslq 中msg为varchar类型)
- mysql 把int msgid转换成float
- 查出的结果异常