前几天在微博上看到一篇文章:价值百万的 MySQL 的隐式类型转换感受写的很不错,再加上本身以前也对MySQL的隐式转化这边并非很清楚,因此就顺势整理了一下。但愿对你们有所帮助。php
当咱们对不一样类型的值进行比较的时候,为了使得这些数值「可比较」(也能够称为类型的兼容性),MySQL会作一些隐式转化(Implicit type conversion)。好比下面的例子:html
mysql> SELECT 1+'1'; -> 2 mysql> SELECT CONCAT(2,' test'); -> '2 test'
很明显,上面的SQL语句的执行过程当中就出现了隐式转化。而且从结果们能够判断出,第一条SQL中,将字符串的“1”转换为数字1,而在第二条的SQL中,将数字2转换为字符串“2”。mysql
MySQL也提供了CAST()
函数。咱们可使用它明确的把数值转换为字符串。当使用CONCA()
函数的时候,也可能会出现隐式转化,由于它但愿的参数为字符串形式,可是若是咱们传递的不是字符串呢:sql
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 isNULL
, except for theNULL
-safe<=>
equality comparison operator. ForNULL <=> NULL
, the result is true. No conversion is needed.安全
If both arguments in a comparison operation are strings, they are compared as strings.ide
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
orDATETIME
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 toIN()
! To be safe, always use complete datetime, date, or time strings when doing comparisons. For example, to achieve best results when usingBETWEEN
with date or time values, useCAST()
to explicitly convert the values to the desired data type.thisA 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 asDATETIME
values, useCAST()
to explicitly convert the subquery value toDATETIME
.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.
翻译为中文就是:
TIMESTAMP
或 DATETIME
,而且另一个参数是常量
,常量会被转换为 timestamp
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)
从上面的例子能够看出,当把字符串转为数字的时候,实际上是从左边开始处理的。
若是你有其余更好的例子,或者被隐式转化坑过的状况,欢迎分享。