MySQL的隐式类型转换整理总结

前言

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

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

比如下面的例子:

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

很明显,上面的SQL语句的执行过程中就出现了隐式转化。并且从结果们可以判断出,第一条SQL中,将字符串的“1”转换为数字1,而在第二条的SQL中,将数字2转换为字符串“2”。

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)

相信上面的例子,一些机灵的同学可以发现其实上面的例子也可以做sql注入。

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

SELECT * FROM users WHERE username = '$_POST["username"]' AND password = '$_POST["password"]'

如果username输入的是a' OR 1='1,那么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)

把字符串“aa”和1进行求和,得到1,因为“aa”和数字1的类型不同,MySQL官方文档告诉我们:

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)

现在就看也很好的理解上面的例子了吧。a+b=c结果为1,1在MySQL中可以理解为TRUE,因为'a'+'b'的结果为0,c也会隐式转化为0,因此比较其实是:0=0也就是true,也就是1.

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

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)

​上面的例子本意是查询id为5的那一条记录,结果把id为6的那一条也查询出来了。我想说明什么情况呢?有时候我们的数据库表中的一些列是varchar类型,但是存储的值为‘1123'这种的纯数字的字符串值,一些同学写sql的时候又不习惯加引号。这样当进行select,update或者delete的时候就可能会多操作一些数据。所以应该加引号的地方别忘记了。

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

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. 如果字符串中存在非数字,那么转换为的数字就是开头的那些数字对应的值

总结

以上就是这篇文章的全部内容了,如果你有其他更好的例子,或者被隐式转化坑过的情况,欢迎分享。希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

(0)

相关推荐

  • 将mysql转换到oracle必须了解的50件事

    另外他们列出的这些区别有些是蛮有意义的,有些可能由于他们本人的MySQL DBA的身份,对Oracle的理解有些偏差,有些则有凑数的嫌疑. 但是总体上结合Baron Schwartz的列表可以让大家大概地了解到Oracle与MySQL的共同之处与差别分别体现在哪些方面.. Robert Treat 1. 有些人说Oracle没有限制,实际上他们说得可能是Oracle不支持LIMIT语法.不过可以rownum虚列来实现,不过会更加麻烦. 2. Oracle不支持偏移(offset)语法. 3. O

  • 深入MYSQL字符数字转换的详解

    1.将字符的数字转成数字,比如'0'转成0可以直接用加法来实现例如:将pony表中的d 进行排序,可d的定义为varchar,可以这样解决select * from pony order by (d+0) 2.在进行ifnull处理时,比如 ifnull(a/b,'0') 这样就会导致 a/b成了字符串,因此需要把'0'改成0,即可解决此困扰 3.比较数字和varchar时,比如a=11,b="11ddddd";则 select 11="11ddddd"相等若绝对比

  • MySQL隐式类型的转换陷阱和规则

    前言 相信大家都知道隐式类型转换有无法命中索引的风险,在高并发.大数据量的情况下,命不中索引带来的后果非常严重.将数据库拖死,继而整个系统崩溃,对于大规模系统损失惨重.所以下面通过本文来好好学习下MySQL隐式类型的转换陷阱和规则. 1. 隐式类型转换实例 今天生产库上突然出现MySQL线程数告警,IOPS很高,实例会话里面出现许多类似下面的sql:(修改了相关字段和值) SELECT f_col3_id,f_qq1_id FROM d_dbname.t_tb1 WHERE f_col1_id=

  • Mysql中文汉字转拼音的实现(每个汉字转换全拼)

    一.创建拼音对照表 复制代码 代码如下: -- 创建汉字拼音对照临时表CREATE TABLE IF NOT EXISTS `t_base_pinyin` (  `pin_yin_` varchar(255) CHARACTER SET gbk NOT NULL,  `code_` int(11) NOT NULL,  PRIMARY KEY (`code_`)) ENGINE=InnoDB DEFAULT CHARSET=latin1; 二.插入对照数据 复制代码 代码如下: -- 插入数据I

  • Mysql 数字类型转换函数

    1.将Int 转为varchar经常用 concat函数,比如concat(8,'0') 得到字符串 '80' 2.将varchar 转为Int 用 cast(a as signed) a为varchar类型的字符串 总结:类型转换和SQL Server一样,就是类型参数有点点不同 : CAST(xxx AS 类型) , CONVERT(xxx,类型) 可用的类型 二进制,同带binary前缀的效果 : BINARY 字符型,可带参数 : CHAR() 日期 : DATE 时间: TIME 日期

  • mysql中将null值转换为0的语句

    复制代码 代码如下: SELECT IF(AVG(cai.conversionsRate) IS NULL,0,AVG(cai.conversionsRate)) AS conversionsRate FROM campaign_info cai WHERE insertTime BETWEEN '2011-02-01' AND '2011-02-04' AND googleCampaignId=23331401

  • MySQL GBK→UTF-8编码转换

    前言: 第一次写教程,其实算不得教程,只是想总结个转换的手记.如果中间有错误,或者办法不够理想,大家回贴研究下. 另外,我也希望我们论坛不仅仅作为闲聊的地方,也希望大家能活跃我们论坛的学习气氛,毕竟我们都来自一个应该给我们知识的地方,不论你从那里获得了多少你需要的知识. 好了,言归正传. 一准备: 环境:MySQL4.1.x及以上版本. Convertz--文本编码转换工具,molyx上介绍的,我采用的.其实这类工具很多. 二理论: MySQL从4.1版本开始内部存储字符集支持了UTF-8,这个

  • MySQL中日期和时间戳互相转换的函数和方法

    ① 时间戳转换成日期 复制代码 代码如下: FROM_UNIXTIME 例如: 数据表中 invest_time 存储的是时间戳,如 1429063399 使用 FROM_UNIXTIME 可以把时间戳转换为日期: 复制代码 代码如下: select FROM_UNIXTIME(invest_time,'%Y年%m月%d') from crm_invest_apply 执行结果: ② 把日期转换为时间戳,和 FROM_UNIXTIME 正好相反 复制代码 代码如下: UNIX_TIMESTAMP

  • mssql转换mysql的方法小结

    方法一: 1.导表结构 使用MySQL生成create脚本的方法.找到生成要导出的脚本,按MySQL的语法修改一下到MySQL数据库中创建该表的列结构什么的. 2.导表数据 在MSSQL端使用bcp导出文本文件: bcp "Select * FROM dbname.dbo.tablename;" queryout tablename.txt -c -Slocalhost\db2005 -Usa 其中""中是要导出的sql语句,-c指定使用\t进行字段分隔,使用\n进

  • PHP+Mysql日期时间如何转换(UNIX时间戳和格式化日期)

    写过PHP+MySQL的程序员都知道有时间差,UNIX时间戳和格式化日期是我们常打交道的两个时间表示形式,Unix时间戳存储.处理方便,但是不直观,格式化日期直观,但是处理起来不如Unix时间戳那么自如,所以有的时候需要互相转换,下面给出互相转换的几种转换方式. 一.在MySQL中完成 这种方式在MySQL查询语句中转换,优点是不占用PHP解析器的解析时间,速度快,缺点是只能用在数据库查询中,有局限性. 1. UNIX时间戳转换为日期用函数: FROM_UNIXTIME() 一般形式:selec

随机推荐