create table t_base_user(
oid bigint(20) not null primary key auto_increment,
name varchar(30) null comment "name",
email varchar(30) null comment "email",
age int null comment "age",
telephone varchar(30) null comment "telephone",
status tinyint(4) null comment "0 無效 1 有效",
created_at datetime null default now() comment "建立時間",
updated_at datetime null default now() comment "修改時間" );
### 新建索引
alter table t_base_user add index idx_oid(oid);
alter table t_base_user add index idx_email(email);
alter table t_base_user add index idx_name(name);
alter table t_base_user add index idx_telephone(telephone);
alter table t_base_user add index idx_telephone_email(telephone,email);
alter table t_base_user add index idx_id_name(oid,name);
alter table t_base_user add index idx_id_created_at(oid,created_at);
alter table t_base_user add unique index idx_id_updated_at(oid,updated_at);
alter table t_base_user add unique index idx_oid2(oid);
### 新增記錄:
INSERT INTO `t_base_user` (`name`, `email`, `age`, `telephone`, `status`, `created_at`, `updated_at`)
VALUES ('111111', 'andytohome@gmail.com', '111', '12345678901', '1', now(),now());
insert into t_base_user(name,email,age,telephone,status) select name,email,age,telephone,status from t_base_user;
### 查詢
explain select * from t_base_user where telephone=12345678901;
explain select * from t_base_user where telephone='12345678901';
explain select * from t_base_user where telephone=cast(12345678901 as char);
explain select * from t_base_user where telephone=cast(12345678901 as char charset latin1);
explain select * from t_base_user where oid='1';
explain select * from t_base_user where oid=1;
--------------------------------------------- Oracle
create table t_base_user(
oid NUMBER(20) not null primary KEY ,
name VARCHAR2(30) ,
email VARCHAR2(30) ,
telephone VARCHAR2(30) );
CREATE INDEX idx_telephone ON t_base_user(telephone);
INSERT INTO t_base_user(oid, name,email,telephone)
VALUES ('111111', 'lhrbest@gmail.com', '111', '12345678901');
set linesize 1000
set autot on
select * from t_base_user where telephone=12345678901;
select * from t_base_user where telephone='12345678901';
---------------------------------------------------------------------------------
mysql> explain select * from t_base_user where telephone=12345678901;
+----+-------------+-------------+------------+------+-----------------------------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------+------------+------+-----------------------------------+------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | t_base_user | NULL | ALL | idx_telephone,idx_telephone_email | NULL | NULL | NULL | 521550 | 10.00 | Using where |
+----+-------------+-------------+------------+------+-----------------------------------+------+---------+------+--------+----------+-------------+
1 row in set, 5 warnings (0.00 sec)
mysql> show warnings;
+---------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1739 | Cannot use ref access on index 'idx_telephone' due to type or collation conversion on field 'telephone' |
| Warning | 1739 | Cannot use ref access on index 'idx_telephone_email' due to type or collation conversion on field 'telephone' |
| Warning | 1739 | Cannot use range access on index 'idx_telephone' due to type or collation conversion on field 'telephone' |
| Warning | 1739 | Cannot use range access on index 'idx_telephone_email' due to type or collation conversion on field 'telephone' |
| Note | 1003 | /* select#1 */ select `lhrdb`.`t_base_user`.`oid` AS `oid`,`lhrdb`.`t_base_user`.`name` AS `name`,`lhrdb`.`t_base_user`.`email` AS `email`,`lhrdb`.`t_base_user`.`age` AS `age`,`lhrdb`.`t_base_user`.`telephone` AS `telephone`,`lhrdb`.`t_base_user`.`status` AS `status`,`lhrdb`.`t_base_user`.`created_at` AS `created_at`,`lhrdb`.`t_base_user`.`updated_at` AS `updated_at` from `lhrdb`.`t_base_user` where (`lhrdb`.`t_base_user`.`telephone` = 12345678901) |
+---------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
5 rows in set (0.00 sec)
mysql> explain select * from t_base_user where telephone='12345678901';
+----+-------------+-------------+------------+------+-----------------------------------+---------------+---------+-------+--------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------+------------+------+-----------------------------------+---------------+---------+-------+--------+----------+-------+
| 1 | SIMPLE | t_base_user | NULL | ref | idx_telephone,idx_telephone_email | idx_telephone | 33 | const | 260775 | 100.00 | NULL |
+----+-------------+-------------+------------+------+-----------------------------------+---------------+---------+-------+--------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql>
mysql> show warnings;
+-------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note | 1003 | /* select#1 */ select `lhrdb`.`t_base_user`.`oid` AS `oid`,`lhrdb`.`t_base_user`.`name` AS `name`,`lhrdb`.`t_base_user`.`email` AS `email`,`lhrdb`.`t_base_user`.`age` AS `age`,`lhrdb`.`t_base_user`.`telephone` AS `telephone`,`lhrdb`.`t_base_user`.`status` AS `status`,`lhrdb`.`t_base_user`.`created_at` AS `created_at`,`lhrdb`.`t_base_user`.`updated_at` AS `updated_at` from `lhrdb`.`t_base_user` where (`lhrdb`.`t_base_user`.`telephone` = '12345678901') |
+-------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> explain select * from t_base_user where telephone=cast(12345678901 as char);
+----+-------------+-------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | t_base_user | NULL | ALL | NULL | NULL | NULL | NULL | 521550 | 100.00 | Using where |
+----+-------------+-------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql>
mysql> show warnings;
+-------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note | 1003 | /* select#1 */ select `lhrdb`.`t_base_user`.`oid` AS `oid`,`lhrdb`.`t_base_user`.`name` AS `name`,`lhrdb`.`t_base_user`.`email` AS `email`,`lhrdb`.`t_base_user`.`age` AS `age`,`lhrdb`.`t_base_user`.`telephone` AS `telephone`,`lhrdb`.`t_base_user`.`status` AS `status`,`lhrdb`.`t_base_user`.`created_at` AS `created_at`,`lhrdb`.`t_base_user`.`updated_at` AS `updated_at` from `lhrdb`.`t_base_user` where (convert(`lhrdb`.`t_base_user`.`telephone` using utf8) = (cast(12345678901 as char charset utf8))) |
+-------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> explain select * from `lhrdb`.`t_base_user` where (convert(`lhrdb`.`t_base_user`.`telephone` using utf8) = cast(12345678901 as char charset utf8));
+----+-------------+-------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | t_base_user | NULL | ALL | NULL | NULL | NULL | NULL | 521550 | 100.00 | Using where |
+----+-------------+-------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> show full columns from lhrdb.t_base_user;
+------------+-------------+-------------------+------+-----+-------------------+----------------+---------------------------------+-------------------+
| Field | Type | Collation | Null | Key | Default | Extra | Privileges | Comment |
+------------+-------------+-------------------+------+-----+-------------------+----------------+---------------------------------+-------------------+
| oid | bigint(20) | NULL | NO | PRI | NULL | auto_increment | select,insert,update,references | |
| name | varchar(30) | latin1_swedish_ci | YES | MUL | NULL | | select,insert,update,references | name |
| email | varchar(30) | latin1_swedish_ci | YES | MUL | NULL | | select,insert,update,references | email |
| age | int(11) | NULL | YES | | NULL | | select,insert,update,references | age |
| telephone | varchar(30) | latin1_swedish_ci | YES | MUL | NULL | | select,insert,update,references | telephone |
| status | tinyint(4) | NULL | YES | | NULL | | select,insert,update,references | 0 無效 1 有效 |
| created_at | datetime | NULL | YES | | CURRENT_TIMESTAMP | | select,insert,update,references | 建立時間 |
| updated_at | datetime | NULL | YES | | CURRENT_TIMESTAMP | | select,insert,update,references | 修改時間 |
+------------+-------------+-------------------+------+-----+-------------------+----------------+---------------------------------+-------------------+
8 rows in set (0.00 sec)
mysql> show index from lhrdb.t_base_user;
+-------------+------------+---------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------------+------------+---------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| t_base_user | 0 | PRIMARY | 1 | oid | A | 521550 | NULL | NULL | | BTREE | | |
| t_base_user | 0 | idx_oid2 | 1 | oid | A | 521550 | NULL | NULL | | BTREE | | |
| t_base_user | 0 | idx_id_updated_at | 1 | oid | A | 521550 | NULL | NULL | | BTREE | | |
| t_base_user | 0 | idx_id_updated_at | 2 | updated_at | A | 521550 | NULL | NULL | YES | BTREE | | |
| t_base_user | 1 | idx_email | 1 | email | A | 521550 | NULL | NULL | YES | BTREE | | |
| t_base_user | 1 | idx_name | 1 | name | A | 521550 | NULL | NULL | YES | BTREE | | |
| t_base_user | 1 | idx_telephone | 1 | telephone | A | 521550 | NULL | NULL | YES | BTREE | | |
| t_base_user | 1 | idx_oid | 1 | oid | A | 521550 | NULL | NULL | | BTREE | | |
| t_base_user | 1 | idx_id_name | 1 | oid | A | 521550 | NULL | NULL | | BTREE | | |
| t_base_user | 1 | idx_id_name | 2 | name | A | 521550 | NULL | NULL | YES | BTREE | | |
| t_base_user | 1 | idx_id_created_at | 1 | oid | A | 521550 | NULL | NULL | | BTREE | | |
| t_base_user | 1 | idx_id_created_at | 2 | created_at | A | 521550 | NULL | NULL | YES | BTREE | | |
| t_base_user | 1 | idx_telephone_email | 1 | telephone | A | 521550 | NULL | NULL | YES | BTREE | | |
| t_base_user | 1 | idx_telephone_email | 2 | email | A | 521550 | NULL | NULL | YES | BTREE | | |
+-------------+------------+---------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
14 rows in set (0.00 sec)
mysql> explain select * from t_base_user where telephone=cast(12345678901 as char charset latin1);
+----+-------------+-------------+------------+------+-----------------------------------+---------------+---------+-------+--------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------+------------+------+-----------------------------------+---------------+---------+-------+--------+----------+-------+
| 1 | SIMPLE | t_base_user | NULL | ref | idx_telephone,idx_telephone_email | idx_telephone | 33 | const | 260775 | 100.00 | NULL |
+----+-------------+-------------+------------+------+-----------------------------------+---------------+---------+-------+--------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> show warnings;
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note | 1003 | /* select#1 */ select `lhrdb`.`t_base_user`.`oid` AS `oid`,`lhrdb`.`t_base_user`.`name` AS `name`,`lhrdb`.`t_base_user`.`email` AS `email`,`lhrdb`.`t_base_user`.`age` AS `age`,`lhrdb`.`t_base_user`.`telephone` AS `telephone`,`lhrdb`.`t_base_user`.`status` AS `status`,`lhrdb`.`t_base_user`.`created_at` AS `created_at`,`lhrdb`.`t_base_user`.`updated_at` AS `updated_at` from `lhrdb`.`t_base_user` where (`lhrdb`.`t_base_user`.`telephone` = cast(12345678901 as char charset latin1)) |
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> explain select * from t_base_user where oid='1';
+----+-------------+-------------+------------+-------+--------------------------------------------------------------------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------+------------+-------+--------------------------------------------------------------------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t_base_user | NULL | const | PRIMARY,idx_oid2,idx_id_updated_at,idx_oid,idx_id_name,idx_id_created_at | PRIMARY | 8 | const | 1 | 100.00 | NULL |
+----+-------------+-------------+------------+-------+--------------------------------------------------------------------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from t_base_user where oid=1;
+----+-------------+-------------+------------+-------+--------------------------------------------------------------------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------+------------+-------+--------------------------------------------------------------------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t_base_user | NULL | const | PRIMARY,idx_oid2,idx_id_updated_at,idx_oid,idx_id_name,idx_id_created_at | PRIMARY | 8 | const | 1 | 100.00 | NULL |
+----+-------------+-------------+------------+-------+--------------------------------------------------------------------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql>
SYS@PROD1> set linesize 1000
SYS@PROD1> set autot on
SYS@PROD1> select * from t_base_user where telephone=12345678901;
OID NAME EMAIL TELEPHONE
---------- ------------------------------ ------------------------------ ------------------------------
111111 lhrbest@gmail.com 111 12345678901
Execution Plan
----------------------------------------------------------
Plan hash value: 3483759010
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 64 | 2 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| T_BASE_USER | 1 | 64 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(TO_NUMBER("TELEPHONE")=12345678901)
Note
-----
- dynamic sampling used for this statement (level=2)
- automatic DOP: Computed Degree of Parallelism is 1 because of parallel threshold
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
633 bytes sent via SQL*Net to client
419 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SYS@PROD1> select * from t_base_user where telephone='12345678901';
OID NAME EMAIL TELEPHONE
---------- ------------------------------ ------------------------------ ------------------------------
111111 lhrbest@gmail.com 111 12345678901
Execution Plan
----------------------------------------------------------
Plan hash value: 1190906832
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 64 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| T_BASE_USER | 1 | 64 | 1 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IDX_TELEPHONE | 1 | | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("TELEPHONE"='12345678901')
Note
-----
- dynamic sampling used for this statement (level=2)
- automatic DOP: Computed Degree of Parallelism is 1 because of parallel threshold
Statistics
----------------------------------------------------------
33 recursive calls
0 db block gets
11 consistent gets
2 physical reads
0 redo size
637 bytes sent via SQL*Net to client
419 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SYS@PROD1>
html
rollenholt 2016-05-06 16:04:42 瀏覽1031 評論0 前端
雲數據庫RDSmysql
摘要: 前幾天在微博上看到一篇文章:價值百萬的 MySQL 的隱式類型轉換感受寫的很不錯,再加上本身以前也對MySQL的隱式轉化這邊並非很清楚,因此就順勢整理了一下。但願對你們有所幫助。 當咱們對不一樣類型的值進行比較的時候,爲了使得這些數值「可比較」(也能夠稱爲類型的兼容性),MySQL會作一些隱式轉化(Implicit type conversion)。sql
前幾天在微博上看到一篇文章:價值百萬的 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」。ide
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.
翻譯爲中文就是:
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)
從上面的例子能夠看出,當把字符串轉爲數字的時候,實際上是從左邊開始處理的。
若是你有其餘更好的例子,或者被隱式轉化坑過的狀況,歡迎分享。
前言:MySQL的隱式轉換是什麼樣子的,何時會進行隱式轉換,下面讓咱們來掀開MySQL隱式轉換的面紗,下面咱們經過一個例子來進行說明
隱式轉換的幾種狀況:
一、當不一樣類型的字段一塊兒使用時,會發生隱式轉換
root@localhost:mysql.sock 16:18:23 [tom]>SELECT 1+'1'; +-------+| 1+'1' | +-------+| 2 | +-------+1 row in set (0.00 sec) root@localhost:mysql.sock 16:18:26 [tom]>SELECT CONCAT(2,' test'); +-------------------+| CONCAT(2,' test') | +-------------------+| 2 test | +-------------------+1 row in set (0.00 sec)
或者使用函數顯示或者隱式進行轉換
root@localhost:mysql.sock 16:29:39 [tom]>SELECT 38.8, CAST(38.8 AS CHAR); +------+--------------------+ | 38.8 | CAST(38.8 AS CHAR) | +------+--------------------+ | 38.8 | 38.8 | +------+--------------------+1 row in set (0.00 sec)root@localhost:mysql.sock 16:30:27 [tom]>SELECT 38.8, CONCAT(38.8); +------+--------------+ | 38.8 | CONCAT(38.8) | +------+--------------+ | 38.8 | 38.8 | +------+--------------+1 row in set (0.00 sec)
二、下面是一些會發生隱式轉換的規則
一、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.
三、下面是字符串轉換成數字進行比較的
root@localhost:mysql.sock 17:23:38 [tom]>SELECT 1 > '6x'; +----------+| 1 > '6x' | +----------+| 0 | +----------+1 row in set, 1 warning (0.00 sec) root@localhost:mysql.sock 17:23:41 [tom]>SELECT 7 > '6x'; +----------+| 7 > '6x' | +----------+| 1 | +----------+1 row in set, 1 warning (0.01 sec) root@localhost:mysql.sock 17:23:48 [tom]>SELECT 0 > 'x6'; +----------+| 0 > 'x6' | +----------+| 0 | +----------+1 row in set, 1 warning (0.00 sec) root@localhost:mysql.sock 17:23:55 [tom]>SELECT 0 = 'x6'; +----------+| 0 = 'x6' | +----------+| 1 | +----------+1 row in set, 1 warning (0.00 sec)
四、若是在sql中對一個字符串和數字比較,mysql不會使用索引
root@localhost:mysql.sock 17:24:03 [tom]>explain select * from test where name = 1;; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | test | NULL | ALL | name | NULL | NULL | NULL | 9 | 11.11 | Using where |+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 3 warnings (0.00 sec)ERROR: No query specified root@localhost:mysql.sock 17:25:42 [tom]>show create table test; +-------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| Table | Create Table | +-------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | test | CREATE TABLE `test` ( `id` int(11) DEFAULT NULL, `age` int(11) DEFAULT NULL, `name` varchar(4) NOT NULL DEFAULT 'tom', KEY `name` (`name`), KEY `id` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 |+-------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+1 row in set (0.00 sec)
The reason for this is that there are many different strings that may convert to the value1, such as‘1’,‘ 1’, or ‘1a’
五、浮點數的比較是近似值,可能致使結果不許確,可能會有取捨
root@localhost:mysql.sock 17:30:19 [tom]>SELECT '18015376320243459'+0.0; +-------------------------+| '18015376320243459'+0.0 | +-------------------------+| 1.801537632024346e16 | +-------------------------+1 row in set (0.00 sec)
浮點數與整數之間的轉換,或者符號之間的轉換最好使用函數CAST(),避免隱式轉換
總結:
一、sql中的where條件禁止進行不一樣字段類型的比較
二、字符串類型和數字比較會轉換成0而後進行比較
三、字符串轉換成數字的時候是從最左邊開始的(不是數字就是0,是數字就匹配最左數字,這就致使可能會多查詢或者刪除、更新數據,這個結果就比較悲催了)
Posted on 2016-05-05 | In MySQL | | Visitors 497
今天生產庫上忽然出現MySQL線程數告警,IOPS很高,實例會話裏面出現許多相似下面的sql:(修改了相關字段和值)
SELECT f_col3_id,f_qq1_id FROM d_dbname.t_tb1 WHERE f_col1_id=1226391 and f_col2_id=1244378 and
f_qq1_id in (12345,23456,34567,45678,56789,67890,78901,89012,90123,901231,901232,901233)
mysql>explain extended SELECT f_col3_id,f_qq1_id FROM d_dbname.t_tb1 use index(idx_corpid_qq1id) WHERE f_col1_id= 1226391 and f_col2_id= 1244378 and f_qq1_id in ( 12345 , 23456 , 34567 , 45678 , 56789 , 67890 , 78901 , 89012 , 90123 , 901231 , 901232 , 901233 ) ;
用 explain 看了下掃描行數和索引選擇狀況:
mysql>explain SELECT f_col3_id,f_qq1_id FROM d_dbname.t_tb1 WHERE f_col1_id=1226391
and f_col2_id=1244378 and f_qq1_id in (12345,23456,34567,45678,56789,67890,78901,89012,90123,901231,901232,901233);
+------+---------------+---------+--------+--------------------------------+---------------+------------+--------+--------+------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+---------------+---------+--------+--------------------------------+---------------+------------+--------+--------+------------------------------------+
| 1 | SIMPLE | t_tb1 | ref | uid_type_frid,idx_corpid_qq1id | uid_type_frid | 8 | const | 1386 | Using index condition; Using where |
+------+---------------+---------+--------+--------------------------------+---------------+------------+--------+--------+------------------------------------+
共返回 1 行記錄,花費 11.52 ms.
t_tb1 表上有個索引uid_type_frid(f_col2_id,f_type)、idx_corp_id_qq1id(f_col1_id,f_qq1_id),並且若是選擇後者時,f_qq1_id的過濾效果應該很佳,但卻選擇了前者。當使用 hint use index(idx_corp_id_qq1id)時:
+------+---------------+--------+--------+---------------------+------------------+------------+----------+-------------+------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+---------------+--------+--------+---------------------+------------------+------------+----------+-------------+------------------------------------+
| 1 | SIMPLE | t_tb1 | ref | idx_corpid_qq1id | idx_corpid_qq1id | 8 | const | 2375752 | Using index condition; Using where |
+---- -+---------------+--------+--------+---------------------+------------------+------------+----------+-------------+------------------------------------+
共返回 1 行記錄,花費 17.48 ms.
mysql>show warnings;
+-----------------+----------------+-----------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-----------------+----------------+-----------------------------------------------------------------------------------------------------------------------+
| Warning | 1739 | Cannot use range access on index 'idx_corpid_qq1id' due to type or collation conversion on field 'f_qq1_id' |
| Note | 1003 | /* select#1 */ select `d_dbname`.`t_tb1`.`f_col3_id` AS `f_col3_id`,`d_dbname`.`t_tb1`.`f_qq1_id` AS `f_qq1_id` from `d_dbname`.`t_tb1` USE INDEX (`idx_corpid_qq1id`) where |
| | | ((`d_dbname`.`t_tb1`.`f_col2_id` = 1244378) and (`d_dbname`.`t_tb1`.`f_col1_id` = 1226391) and (`d_dbname`.`t_tb1`.`f_qq1_id` in |
| | | (12345,23456,34567,45678,56789,67890,78901,89012,90123,901231,901232,901233))) |
+-----------------+----------------+-----------------------------------------------------------------------------------------------------------------------+
共返回 2 行記錄,花費 10.81 ms.
rows列達到200w行,但問題也發現了:select_type應該是 range 纔對,key_len看出來只用到了idx_corpid_qq1id索引的第一列。上面explain使用了 extended,因此show warnings;能夠很明確的看到 f_qq1_id 出現了隱式類型轉換:f_qq1_id是varchar,然後面的比較值是整型。
解決該問題就是避免出現隱式類型轉換(implicit type conversion)帶來的不可控:把f_qq1_id in的內容寫成字符串:
mysql>explain SELECT f_col3_id,f_qq1_id FROM d_dbname.t_tb1 WHERE f_col1_id=1226391 and f_col2_id=1244378 and
f_qq1_id in ('12345','23456','34567','45678','56789','67890','78901','89012','90123','901231');
+-------+---------------+--------+---------+--------------------------------+------------------+-------------+---------+---------+------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+-------+---------------+--------+---------+--------------------------------+------------------+-------------+---------+---------+------------------------------------+
| 1 | SIMPLE | t_tb1 | range | uid_type_frid,idx_corpid_qq1id | idx_corpid_qq1id | 70 | | 40 | Using index condition; Using where |
+-------+---------------+--------+---------+--------------------------------+------------------+-------------+---------+---------+------------------------------------+
共返回 1 行記錄,花費 12.41 ms.
相似的還出現過一例:掃描行數從1386減小爲40。
SELECT count(0) FROM d_dbname.t_tb2 where f_col1_id= '1931231' AND f_phone in(098890);
| Warning | 1292 | Truncated incorrect DOUBLE value: '1512-98464356'
借這個機會,系統的來看一下mysql中的隱式類型轉換。優化後直接從掃描rows 100w行降爲1。
下面來分析一下 隱式轉換的規則 :
a. 兩個參數至少有一個是 NULL 時,比較的結果也是 NULL,例外是使用 <=> 對兩個 NULL 作比較時會返回 1,這兩種狀況都不須要作類型轉換
b. 兩個參數都是字符串,會按照字符串來比較,不作類型轉換
c. 兩個參數都是整數,按照整數來比較,不作類型轉換
d. 十六進制的值和非數字作比較時,會被當作二進制串
e. 有一個參數是 TIMESTAMP 或 DATETIME,而且另一個參數是常量,常量會被轉換爲 timestamp
f. 有一個參數是 decimal 類型,若是另一個參數是 decimal 或者整數,會將整數轉換爲 decimal 後進行比較,若是另一個參數是浮點數,則會把 decimal 轉換爲浮點數進行比較
g. 全部其餘狀況下,兩個參數都會被轉換爲浮點數再進行比較
mysql> select 11 + '11', 11 + 'aa', 'a1' + 'bb', 11 + '0.01a';
+-----------+-----------+-------------+--------------+
| 11 + '11' | 11 + 'aa' | 'a1' + 'bb' | 11 + '0.01a' |
+-----------+-----------+-------------+--------------+
| 22 | 11 | 0 | 11.01 |
+-----------+-----------+-------------+--------------+
1 row in set, 4 warnings (0.00 sec)
mysql> show warnings;
+---------+------+-------------------------------------------+
| Level | Code | Message |
+---------+------+-------------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'aa' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a1' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'bb' |
| Warning | 1292 | Truncated incorrect DOUBLE value: '0.01a' |
+---------+------+-------------------------------------------+
4 rows in set (0.00 sec)
mysql> select '11a' = 11, '11.0' = 11, '11.0' = '11', NULL = 1;
+------------+-------------+---------------+----------+
| '11a' = 11 | '11.0' = 11 | '11.0' = '11' | NULL = 1 |
+------------+-------------+---------------+----------+
| 1 | 1 | 0 | NULL |
+------------+-------------+---------------+----------+
1 row in set, 1 warning (0.01 sec)
0.01a轉成double型也是被截斷成0.01,因此11 + '0.01a' = 11.01。上面能夠看出11 + 'aa',因爲操做符兩邊的類型不同且符合第g條,aa要被轉換成浮點型小數,然而轉換失敗(字母被截斷),能夠認爲轉成了 0,整數11被轉成浮點型仍是它本身,因此11 + 'aa' = 11。
等式比較也說明了這一點,'11a'和'11.0'轉換後都等於 11,這也正是文章開頭實例爲何沒走索引的緣由: varchar型的f_qq1_id,轉換成浮點型比較時,等於 12345 的狀況有無數種如12345a、12345.b等待,MySQL優化器沒法肯定索引是否更有效,因此選擇了其它方案。
但並非只要出現隱式類型轉換,就會引發上面相似的性能問題,最終是要看轉換後可否有效選擇索引。像f_id = '654321'、f_mtime between '2016-05-01 00:00:00' and '2016-05-04 23:59:59'就不會影響索引選擇,由於前者f_id是整型,即便與後面的字符串型數字轉換成double比較,依然能根據double肯定f_id的值,索引依然有效。後者是由於符合第e條,只是右邊的常量作了轉換。
開發人員可能都只要存在這麼一個隱式類型轉換的坑,但卻又常常不注意,因此乾脆無需記住那麼多規則,該什麼類型就與什麼類型比較。
implicit type conversion 不只可能引發性能問題,還有可能產生安全問題。
mysql> desc t_account;
+-----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+----------------+
| fid | int(11) | NO | PRI | NULL | auto_increment |
| fname | varchar(20) | YES | | NULL | |
| fpassword | varchar(50) | YES | | NULL | |
+-----------+-------------+------+-----+---------+----------------+
mysql> select * from t_account;
+-----+-----------+-------------+
| fid | fname | fpassword |
+-----+-----------+-------------+
| 1 | xiaoming | p_xiaoming |
| 2 | xiaoming1 | p_xiaoming1 |
+-----+-----------+-------------+
假如應用前端沒有WAF防禦,那麼下面的sql很容易注入:
mysql> select * from t_account where fname='A' ;
fname傳入 A' OR 1='1
mysql> select * from t_account where fname='A' OR 1='1';
***者更聰明一點: fname傳入 A'+'B ,fpassword傳入 ccc'+0 :
mysql> select * from t_account where fname='A'+'B' and fpassword='ccc'+0;
+-----+-----------+-------------+
| fid | fname | fpassword |
+-----+-----------+-------------+
| 1 | xiaoming | p_xiaoming |
| 2 | xiaoming1 | p_xiaoming1 |
+-----+-----------+-------------+
2 rows in set, 7 warnings (0.00 sec)