该篇文章根据网上的资料跟本人编写SQL的那一点点经验总结出来的,如果有什么不准确的地方,请指正,感谢感谢!
避免潜在的数据类型转换
在MySQL跟Oracle中,如果存在隐式的数据类型转换,可能导致无法命中索引,从而进行全表扫描的危险。
例如:
错误范例(col1为字符串类型):
select col1,col2 from tab1 where col1 > 10
改善写法:
select col1,col2 from tab1 where col1 > '10'
MySQL类型转换规则:
- 两个参数至少有一个是NULL时,比较的结果也是 NULL,例外是使用 <=> 对两个NULL做比较时会返回1,这两种情况都不需要做类型转换
- 两个参数都是字符串,会按照字符串来比较,不做类型转换
- 两个参数都是整数,按照整数来比较,不做类型转换
- 十六进制的值和非数字做比较时,会被当做二进制串
- 有一个参数是TIMESTAMP或DATETIME,并且另外一个参数是常量,常量会被转换为timestamp
- 所有其他情况下,两个参数都会被转换为浮点数再进行比较
对于最后一条规则,请看:
原因是两边的类型不一致,引发类型转换,把两边的操作数都转换成浮点数,如果字符串以数字开头,在转换时截取前面的数字进行转换,如果字符串不是以数字开头,那么被转换成0,可以看到图中的wms_password字段的值不是以数字开头,所以被转换成0,于是改记录被捞出来了。
哪些类型转换会引起全表扫描? 哪些不会呢?
在mysql5.7.17下测试一下。
看看表结构:
看看数据:
看字符—>数值
执行计划:
explain select * from wms_user where user_username='11';
可以看到该SQL查询时使用到了索引username_index,这里不需要进行隐式的类型转换,再看一个进行隐式类型转换的。
explain select * from wms_user where user_username=11;
这里的type为ALL,表示Full Table Scan, MySQL将进行全表扫描。possible_keys是username_index,表示可以使用该索引,若查询涉及到的字段上存在索引,则该索引将被列出,但这里并没有使用。key为null,说明没有使用到索引。
看数值—>字符串
执行计划:
explain select * from wms_user where user_first_login=1;
- type为ref表示非唯一性索引扫描,返回匹配某个单独值的所有行。常见于使用非唯一索引或唯一索引的非唯一前缀进行的查找。
- possible_keys是login_index表示该字段上有索引login_index。
- key为显示login_index表示在查询中实际使用的索引是login_index。
再看:
explain select * from wms_user where user_first_login='1';
可以看到这里依旧走了索引。
我的理解是select * from wms_user where user_username=11
是对user_username栏位进行类型转换,是在列上面进行的,需要把每一列的值都进行转换,所以使用不了索引;
而select * from wms_user where user_first_login='1'
是直接对字符’1’进行转换的,所以可以使用索引,该结论根据规则6得来。
注意:如果过滤条件不能过滤掉部分数据,也就是过滤后匹配的数据占据了表的大部分数据,那么mysql将不会走索引(mysql5.7.17)。
Oracle类型转换规则:
1、对于insert和update操作,oracle将值转换为受影响的的列的类型。
2、对于SELECT操作,oracle会将列的值的类型转换为目标变量的类型。
对于该规则的理解,看下面的测试:
表结构:
create table WMS_USER
(
USER_ID INTEGER not null,
USER_USERNAME VARCHAR2(30) not null,
USER_PASSWORD VARCHAR2(40) not null,
USER_FIRST_LOGIN INTEGER not null
);
create index LOGIN_INDEX on WMS_USER (WMS_USER);
create index USERNAME_INDEX on WMS_USER (USER_USERNAME);
可以看到在第二句SQL中,用TO_NUMBER函数对USER_USERNAME进行了转型,使用不了索引。
3、当字符串类型和数值类型做比较时,oracle会将字符串类型转换为数值类型。
因为把字符‘1’转换成数值类型,所以不需要对列进行转换,可以使用索引。
4、当字符串类型和日期类型做比较时,oracle会将字符串类型转换为日期类型。
可以看到对列进行了隐式类型转换,所以使用不了索引,进行了全表扫描。
5、如果调用函数或存储过程等时,如果输入参数的数据类型与函数或者存储过程定义的参数数据类型不一直,则oracle会把输入参数的数据类型转换为函数或者过程定义的数据类型。
6、用连接操作符||时,oracle会把非字符类型的数据转换为字符类型。
7、如果字符类型的数据和非字符类型的数据(如number、date、rowid等)作算术运算,则oracle会将字符类型的数据转换为合适的数据类型,这些数据类型可能是number、date、rowid等。
如果CHAR/VARCHAR2和NCHAR/NVARCHAR2之间作算术运算,则oracle会将它们都转换为number类型的数据再做比较。
8、比较CHAR/VARCHAR2 和NCHAR/NVARCHAR2时,如果两者字符集不一样,则默认的转换方式是将数据编码从数据库字符集转换为国家字符集。