加了索引,为何却不生效

本文分析了数据库索引失效的常见原因,包括索引列在表达式中、隐式类型转换、隐式编码转换以及使用ORDER BY造成的全表扫描,并提供了相应的解决策略,如避免函数操作索引列、显式转换类型和优化查询语句。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

加了索引却不生效可能会有以下几种原因

1、索引列是表示式的一部分,或是函数的一部分

如下 SQL:

SELECT book_id FROM BOOK WHERE book_id + 1 = 5; 

 或者

SELECT book_id FROM BOOK WHERE TO_DAYS(CURRENT_DATE) - TO_DAYS(gmt_create) <= 10

 上述两个 SQL 虽然在列 book_id 和 gmt_create 设置了索引 ,但由于它们是表达式或函数的一部分,导致索引无法生效,最终导致全表扫描。

2、隐式类型转换

以上两种情况相信不少人都知道索引不能生效,但下面这种隐式类型转换估计会让不少人栽跟头,来看下下面这个例子:

假设有以下表:

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;

执行 SQL 语句

 SELECT * FROM tradelog WHERE tradeid=110717;

 交易编号 tradeid 上有索引,但用 EXPLAIN 执行却发现使用了全表扫描,为啥呢,tradeId 的类型是 varchar(32), 而此 SQL 用 tradeid 一个数字类型进行比较,发生了隐形转换,会隐式地将字符串转成整型,如下:

mysql> SELECT * FROM tradelog WHERE CAST(tradid AS signed int) = 110717;

 这样也就触发了上文中第一条的规则 ,即:索引列不能是函数的一部分。

3、隐式编码转换

这种情况非常隐蔽,来看下这个例子

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;

 trade_defail 是交易详情, tradelog 是操作此交易详情的记录,现在要查询 id=2 的交易的所有操作步骤信息,则我们会采用如下方式

SELECT d.* FROM tradelog l, trade_detail d WHERE d.tradeid=l.tradeid AND l.id=2;

 由于 tradelog 与 trade_detail 这两个表的字符集不同,且 tradelog 的字符集是 utf8mb4,而 trade_detail 字符集是 utf8, utf8mb4 是 utf8 的超集,所以会自动将 utf8 转成 utf8mb4。即上述语句会发生如下转换:

SELECT d.* FROM tradelog l, trade_detail d WHERE (CONVERT(d.traideid USING utf8mb4)))=l.tradeid AND l.id=2;

 自然也就触发了 「索引列不能是函数的一部分」这条规则。怎么解决呢,第一种方案当然是把两个表的字符集改成一样,如果业务量比较大,生产上不方便改的话,还有一种方案是把 utf8mb4 转成 utf8,如下

mysql> SELECT d.* FROM tradelog l , trade_detail d WHERE d.tradeid=CONVERT(l.tradeid USING utf8) AND l.id=2; 

这样索引列就生效了。 

4、使用 order by 造成的全表扫描

SELECT * FROM user ORDER BY age DESC

 上述语句在 age 上加了索引,但依然造成了全表扫描,这是因为我们使用了 SELECT *,导致回表查询,MySQL 认为回表的代价比全表扫描更大,所以不选择使用索引,如果想使用到 age 的索引,我们可以用覆盖索引来代替:

SELECT age FROM user ORDER BY age DESC

或者加上 limit 的条件(数据比较小)

 SELECT * FROM user ORDER BY age DESC limit 10

这样就能利用到索引。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值