前幾天在微博上看到一篇文章:價值百萬的 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)
從上面的例子能夠看出,當把字符串轉爲數字的時候,實際上是從左邊開始處理的。
若是你有其餘更好的例子,或者被隱式轉化坑過的狀況,歡迎分享。