《MySQL tips:隐式类型转换与隐式字符编码转换对查询效率的影响》

维护一个交易系统,交易记录表tradelog包含交易流水号(tradeid)、交易员id(operator)、交易时间(t_modified)等字段。

create table 'tradelog' (
	'id' int(11) not null,
	'tradeid' varchar(32) default null,
	'operator' int(11) default null,
	't_modified' datetime default null,
	primary key ('id'),
	key 'tradeid' ('tradeid'),
	key 't_modified' ('t_modified')
) engine = InnoDB default charset = utf8mb4;

隐式类型转换

select * from tradelog where tradeid = 110717;

交易编号tradeid字段上本身就有索引。但是explain这条语句后显示,这条语句走的时全表扫描。
tradeid 的字段类型是varchar(32),而输入的参数却是整型,所以需要做类型转换

字符串与数字做比较的话,将字符串转换成数字
所以之前的语句被转换成:

select * from tradelog where CAST(tradeid as signed int) = 110717;

这个会触发规则:对索引字段做函数操作,优化器会放弃走树搜索功能。
如果我们的语句是如下的:

select * from tradelog where id = "83126";

此时不会触发规则。因为字符串和数字比较,会将字符串转换成数字。现在字符串在比较符右边,只会把右边转换成数字,所以不会发生转换,所以仍然走树搜索。

隐式字符编码转换

假设系统里有表trade_detail,用于记录交易细节。
我们往交易日志表tradelog和交易详情表trade_detail这两个表里插入一些数据。


mysql> CREATE TABLE `trade_detail` (
  `id` int(11) NOT NULL,
  `tradeid` varchar(32) DEFAULT NULL,
  `trade_step` int(11) DEFAULT NULL, /*操作步骤*/
  `step_info` varchar(32) DEFAULT NULL, /*步骤信息*/
  PRIMARY KEY (`id`),
  KEY `tradeid` (`tradeid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into tradelog values(1, 'aaaaaaaa', 1000, now());
insert into tradelog values(2, 'aaaaaaab', 1000, now());
insert into tradelog values(3, 'aaaaaaac', 1000, now());

insert into trade_detail values(1, 'aaaaaaaa', 1, 'add');
insert into trade_detail values(2, 'aaaaaaaa', 2, 'update');
insert into trade_detail values(3, 'aaaaaaaa', 3, 'commit');
insert into trade_detail values(4, 'aaaaaaab', 1, 'add');
insert into trade_detail values(5, 'aaaaaaab', 2, 'update');
insert into trade_detail values(6, 'aaaaaaab', 3, 'update again');
insert into trade_detail values(7, 'aaaaaaab', 4, 'commit');
insert into trade_detail values(8, 'aaaaaaac', 1, 'add');
insert into trade_detail values(9, 'aaaaaaac', 2, 'update');
insert into trade_detail values(10, 'aaaaaaac', 3, 'update again');
insert into trade_detail values(11, 'aaaaaaac', 4, 'commit');

调用该sql语句:

select d.* from tradelog l,trade_detail d where d.tradeid = l.tradeid and l.id = 2;

其执行步骤为:
1、根据id在tradelog表里找到L2这一行
2、从L2中取出tradeid字段的值
3、根据tradeid值到trade_detail表中查找条件匹配的行。该过程是通过遍历主键索引的方式,一个一个地判断tradeid地值是否匹配。

这里的第3步不符合我们的预期,因为trade_detail里tradeid字段是有索引的,我们本来是希望通过使用tradeid索引能够快速定位到等值的行。但是这里显然不是这样做的。
这是因为两个表的字符集不同,一个是utf8,一个是utf8mb4,所以做表连接查询的时候用不上关联字段的索引。
将执行步骤的第三步单独拎出来改成SQL语句:

select * from trade_detail where tradeid = $L2.tradeid.value;

$L2.tradeid.value的字符集是utf8mb4;
utf8mb4是utf8的超集,所以当这两个类型的字符串在做比较的时候,MySQL内部的操作是先将utf8转换成utf8mb4字符集,再做比较。
也相当于:

select * from trade_detail where convert(tradeid using utf8mb4) = $L2.tradeid.value;

这就会触发:对索引字段做函数操作,优化器会放弃树搜索功能。
这就是不同字符集连接查询,关联字段不走索引的原因。

猜你喜欢

转载自blog.csdn.net/qq_42604176/article/details/115419484