前言

前几天在看到一篇文章:价值百万的 MySQL 的隐式类型转换感觉写的很不错,再加上自己之前也对MySQL的隐式转化这边并不是很清楚,所以就顺势整理了一下。希望对大家有所帮助。

当我们对不同类型的值进行比较的时候,为了使得这些数值「可比较」(也可以称为类型的兼容性),MySQL会做一些隐式转化(Implicit type conversion)。

比如下面的例子:

mysql> SELECT 1+'1'; -> 2mysql> SELECT CONCAT(2,' test'); -> '2 test'

MySQL也提供了CAST()函数。我们可以使用它明确的把数值转换为字符串。当使用CONCA()函数的时候,也可能会出现隐式转化,因为它希望的参数为字符串形式,但是如果我们传递的不是字符串呢:

mysql> SELECT 38.8, CAST(38.8 AS CHAR); -> 38.8, '38.8'mysql> SELECT 38.8, CONCAT(38.8); -> 38.8, '38.8'

官方文档中关于隐式转化的规则是如下描述的:

If one or both arguments are NULL, the result of the comparison is NULL, except for the NULL-safe <=> equality comparison operator. For NULL <=> NULL, the result is true. No conversion is needed.

  • If both arguments in a comparison operation are strings, they are compared as strings.
  • If both arguments are integers, they are compared as integers.
  • Hexadecimal values are treated as binary strings if not compared to a number.
  • If one of the arguments is a TIMESTAMP or DATETIME column and the other argument is a constant, the constant is converted to a timestamp before the comparison is performed. This is done to be more ODBC-friendly. Note that this is not done for the arguments to IN()! To be safe, always use complete datetime, date, or time strings when doing comparisons. For example, to achieve best results when using BETWEEN with date or time values, use CAST() to explicitly convert the values to the desired data type.
    A single-row subquery from a table or tables is not considered a constant. For example, if a subquery returns an integer to be compared to a DATETIME value, the comparison is done as two integers. The integer is not converted to a temporal value. To compare the operands as DATETIME values, use CAST() to explicitly convert the subquery value to DATETIME.
  • If one of the arguments is a decimal value, comparison depends on the other argument. The arguments are compared as decimal values if the other argument is a decimal or integer value, or as floating-point values if the other argument is a floating-point value.
  • In all other cases, the arguments are compared as floating-point (real) numbers.

翻译为中文就是:

  1. 两个参数至少有一个是 NULL 时,比较的结果也是 NULL,例外是使用 <=> 对两个 NULL 做比较时会返回 1,这两种情况都不需要做类型转换
  2. 两个参数都是字符串,会按照字符串来比较,不做类型转换
  3. 两个参数都是整数,按照整数来比较,不做类型转换
  4. 十六进制的值和非数字做比较时,会被当做二进制串
  5. 有一个参数是 TIMESTAMP 或 DATETIME,并且另外一个参数是常量,常量会被转换为 timestamp
  6. 有一个参数是 decimal 类型,如果另外一个参数是 decimal 或者整数,会将整数转换为 decimal 后进行比较,如果另外一个参数是浮点数,则会把 decimal 转换为浮点数进行比较
  7. 所有其他情况下,两个参数都会被转换为浮点数再进行比较

注意点

安全问题:假如 password 类型为字符串,查询条件为 int 0 则会匹配上。

mysql> select * from test;+----+-------+-----------+| id | name | password |+----+-------+-----------+| 1 | test1 | password1 || 2 | test2 | password2 |+----+-------+-----------+2 rows in set (0.00 sec)mysql> select * from test where name = 'test1' and password = 0;+----+-------+-----------+| id | name | password |+----+-------+-----------+| 1 | test1 | password1 |+----+-------+-----------+1 row in set, 1 warning (0.00 sec)mysql> show warnings;+---------+------+-----------------------------------------------+| Level | Code | Message   |+---------+------+-----------------------------------------------+| Warning | 1292 | Truncated incorrect DOUBLE value: 'password1' |+---------+------+-----------------------------------------------+1 row in set (0.00 sec)

假设网站的登录那块做的比较挫,使用下面的方式:

SELECT * FROM users WHERE username = '$_POST["username"]' AND password = '$_POST["password"]'
SELECT * FROM users WHERE username = 'a' OR 1='1' AND password = 'anyvalue'
mysql> select * from test;+----+-------+-----------+| id | name | password |+----+-------+-----------+| 1 | test1 | password1 || 2 | test2 | password2 || 3 | aaa | aaaa || 4 | 55aaa | 55aaaa |+----+-------+-----------+4 rows in set (0.00 sec)mysql> select * from test where name = 'a' + '55';+----+-------+----------+| id | name | password |+----+-------+----------+| 4 | 55aaa | 55aaaa |+----+-------+----------+1 row in set, 5 warnings (0.00 sec)
mysql> select '55aaa' = 55;+--------------+| '55aaa' = 55 |+--------------+| 1 |+--------------+1 row in set, 1 warning (0.00 sec)mysql> select 'a' + '55';+------------+| 'a' + '55' |+------------+| 55 |+------------+1 row in set, 1 warning (0.00 sec)
mysql> select 1+1;+-----+| 1+1 |+-----+| 2 |+-----+1 row in set (0.00 sec)mysql> select 'aa' + 1;+----------+| 'aa' + 1 |+----------+| 1 |+----------+1 row in set, 1 warning (0.00 sec)mysql> show warnings;+---------+------+----------------------------------------+| Level | Code | Message  |+---------+------+----------------------------------------+| Warning | 1292 | Truncated incorrect DOUBLE value: 'aa' |+---------+------+----------------------------------------+1 row in set (0.00 sec)

When an operator is used with operands of different types, type conversion occurs to make the operands compatible.

查看warnings可以看到隐式转化把字符串转为了double类型。但是因为字符串是非数字型的,所以就会被转换为0,因此最终计算的是0+1=1

上面的例子是类型不同,所以出现了隐式转化,那么如果我们使用相同类型的值进行运算呢?

mysql> select 'a' + 'b';+-----------+| 'a' + 'b' |+-----------+|  0 |+-----------+1 row in set, 2 warnings (0.00 sec)mysql> show warnings;+---------+------+---------------------------------------+| Level | Code | Message    |+---------+------+---------------------------------------+| Warning | 1292 | Truncated incorrect DOUBLE value: 'a' || Warning | 1292 | Truncated incorrect DOUBLE value: 'b' |+---------+------+---------------------------------------+2 rows in set (0.00 sec)

之所以出现这种情况,是因为+为算术操作符arithmetic operator 这样就可以解释为什么a和b都转换为double了。因为转换之后其实就是:0+0=0了。

再看一个例子:

mysql> select 'a'+'b'='c';+-------------+| 'a'+'b'='c' |+-------------+|  1 |+-------------+1 row in set, 3 warnings (0.00 sec)mysql> show warnings;+---------+------+---------------------------------------+| Level | Code | Message    |+---------+------+---------------------------------------+| Warning | 1292 | Truncated incorrect DOUBLE value: 'a' || Warning | 1292 | Truncated incorrect DOUBLE value: 'b' || Warning | 1292 | Truncated incorrect DOUBLE value: 'c' |+---------+------+---------------------------------------+3 rows in set (0.00 sec)

第二个需要注意点就是防止多查询或者删除数据

mysql> select * from test;+----+-------+-----------+| id | name | password |+----+-------+-----------+| 1 | test1 | password1 || 2 | test2 | password2 || 3 | aaa | aaaa || 4 | 55aaa | 55aaaa || 5 | 1212 | aaa || 6 | 1212a | aaa |+----+-------+-----------+6 rows in set (0.00 sec)mysql> select * from test where name = 1212;+----+-------+----------+| id | name | password |+----+-------+----------+| 5 | 1212 | aaa || 6 | 1212a | aaa |+----+-------+----------+2 rows in set, 5 warnings (0.00 sec)mysql> select * from test where name = '1212';+----+------+----------+| id | name | password |+----+------+----------+| 5 | 1212 | aaa |+----+------+----------+1 row in set (0.00 sec)

关于字符串转数字的一些说明

mysql> select 'a' = 0;+---------+| 'a' = 0 |+---------+| 1 |+---------+1 row in set, 1 warning (0.00 sec)mysql> select '1a' = 1;+----------+| '1a' = 1 |+----------+| 1 |+----------+1 row in set, 1 warning (0.00 sec)mysql> select '1a1b' = 1;+------------+| '1a1b' = 1 |+------------+|  1 |+------------+1 row in set, 1 warning (0.00 sec)mysql> select '1a2b3' = 1;+-------------+| '1a2b3' = 1 |+-------------+|  1 |+-------------+1 row in set, 1 warning (0.00 sec)mysql> select 'a1b2c3' = 0;+--------------+| 'a1b2c3' = 0 |+--------------+|  1 |+--------------+1 row in set, 1 warning (0.00 sec)
  1. 如果字符串的第一个字符就是非数字的字符,那么转换为数字就是0
  2. 如果字符串以数字开头
  3. 如果字符串中都是数字,那么转换为数字就是整个字符串对应的数字
  4. 如果字符串中存在非数字,那么转换为的数字就是开头的那些数字对应的值

总结

更多相关文章

  1. Android开发从零开始之java-泛型初步
  2. 链接器解析多重定义的全局变量
  3. android Uri获取真实路径转换成File的方法
  4. Android(安卓)识别SIM卡类型
  5. android 判断联网类型
  6. Android电池信息(Battery information)
  7. 安卓4.X系统 增加蓝牙接收文件类型
  8. JS判断终端类型的几种方法
  9. Android(安卓)泛型使用

随机推荐

  1. 第一次发帖望各位大神帮顶啊!mini2440上的
  2. 堆栈/帧指针作为外部变量
  3. Linux最常用的基础命令 上篇
  4. Linux回调函数的应用---已经验证
  5. Ubuntu 一键安装下LAMP安装配置
  6. 解决nfs链接开发板出现:nfs:server is not
  7. fedora(linux)创建系统服务 程序开机自启
  8. Android(安卓)Jetpack 使用入门
  9. Nagios 监控 Linux 服务器
  10. 创建链表的小例子