前言
前幾天在看到一篇文章:價值百萬的 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.
翻譯為中文就是:
注意點
安全問題:假如 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)
從上面的例子可以看出,當把字符串轉為數字的時候,其實是從左邊開始處理的。
總結
以上就是這篇文章的全部內容了,如果你有其他更好的例子,或者被隱式轉化坑過的情況,歡迎分享。希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。