65.mysql索引失效优化以及解决

282 阅读9分钟

索引失效优化

select * 导致索引失效

覆盖索引:当sql语句的所求查询字段(select列)和查询条件字段(where子句)全都包含在一个索引中(联合索引),可以直接使用索引查询而不需要回表,这就是覆盖索引。

使用select * 会导致以下问题:

  • 增加查询分析器解析成本。
  • 无用字段增加网络传输成本,尤其是text 类型的字段。
  • 无法使用覆盖索引

之所以说使用select * 会导致索引失效,是因为在某些场景下某些sql语句可以使用覆盖索引进行优化。使用了select * 就没法使用覆盖索引了。

列区分度太低导致不走索引

优化 像sex、逻辑删除、这种列就不要加索引了。

联合索引违反最左前缀或者断层

MySQL可以为多个字段创建索引,一个索引可以包括16个字段

对于联合索引,只有查询条件中使用了这些字段中第一个字段时,索引才会生效。

例如索引是key index (a,b,c)。可以支持a | a,b| a,b,c 3种组合进行查找,但不支持 b,c进行查找 。当最左侧字段是常量引用时,索引就十分有效。

 create table myTest(
 
         a int,
 
         b int,
 
         c int,
 
         KEY a(a,b,c)
 
    );
select * from myTest  where a=3 and b=5 and c=4; ----  abc顺序 

abc三个索引都在where条件里面用到了,而且都发挥了作用

select * from myTest  where  c=4 and b=6 and a=3; 

where里面的条件顺序在查询之前会被mysql自动优化,效果跟上一句一样。

select * from myTest  where a=3 and c=7; 

a用到索引,b没有用,所以c是没有用到索引效果的

select * from myTest  where a=3 and b>7 and c=3; ---- b范围值,断点,阻塞了c的索引 

a用到了,b也用到了,c没有用到,这个地方b是范围值,也算断点,只不过自身用到了索引

select * from myTest  where b=3 and c=4;   --- 违反最左前缀

因为a索引没有使用,所以这里 bc都没有用上索引效果

select * from myTest  where a>4 and b=7 and c=9; 

a用到了 b没有使用,c没有使用

select * from myTest  where a=3 order by b; 

a用到了索引,b在结果排序中也用到了索引的效果,a下面任意一段的b是排好序的

select * from myTest  where a=3 order by c; 

a用到了索引,但是这个地方c没有发挥排序效果,因为中间断点了,使用 explain 可以看到 filesort

select * from mytable where b=3 order by a; 

b没有用到索引,排序中a也没有发挥索引效果

关于联合索引为何会失效:juejin.cn/editor/draf…

优化:使之符合最左前缀原则

使用LIKE关键字的查询:%在前

SELECT
    *
FROM
    single_table s1  
WHERE
    s1.key1 like  '%a%';

优化:使用覆盖索引,可以优化查询

SELECT
    id,key1 
FROM
    single_table s1  
WHERE
    s1.key1 like  '%a%';

哈哈,虽然用覆盖索引可以优化,但是估计也只是用了索引下推而已,没有做全表扫描,走的是扫描索引树。

所以这个方案不建议使用。

优化:索引下推

比如 name,city列

现在需要对name和city做条件查询

where name = '李四' and city like '%河南'

此时可以用到(city,name)索引

先查找到name索引列符合条件的数据,然后再过滤

优化:使用instr(field, str) 函数

mysql 进行模糊查询时,可使用内部函数 instr,替代传统的 like 方式,并且速度更快。

instr(field, str) 函数,第一个参数 field 是字段,第二个参数 str 是要查询的串,返回串 str 的位置,没找到就是0。

SELECT
    id,key1 
FROM
    single_table s1  
WHERE
    instr(s1.key1,'a')>0

实际没有验证过

优化:新增一列,存储该字段的反转。

比如数据有记录: name=彭方亮,亮方彭

现在的需求是查询%彭方亮,那么我们只需要查询亮方彭%

关于like右边有%为什么可以走索引:juejin.cn/editor/draf…

索引列有运算

select * from user where id/2=100

优化:索引列运算转为对输入参数运算

select * from user where id=100*2

索引列使用了函数

select * from user where id/2=100

优化:将索引列函数,转为输入参数上

索引列数据类型隐式转换

数据类型转换的规则是什么?为什么有数据类型转换,就需要走全索引扫描?

先来看第一个问题,你可能会说,数据库里面类型这么多,这种数据类型转换规则更多,我记不住,应该怎么办呢?

这里有一个简单的方法,看 select “10” > 9的结果:

如果规则是“将字符串转成数字”,那么就是做数字比较,结果应该是1;

如果规则是“将数字转成字符串”,那么就是做字符串比较,结果应该是0。

select “10” > 9返回的是1,

所以你就能确认MySQL转换规则:在MySQL中,字符串和数字做比较的话,是将字符串转换成数字。

 select * from tradelog where tradeid=110717;
​
交易编号tradeid这个字段上,本来就有索引,但是explain的结果却显示,这条语句需要走全表扫描。你可能也发现了,tradeid的字段类型是varchar(32),而输入的参数却是整型,所以需要做类型转换。
​
所以先将tradid列 由字符串转为int类型在比较
mysql> select * from tradelog where  CAST(tradid AS signed int) = 110717;

优化:使用和索引列一样的数据类型,将数据类型转换放在输入参数上

索引列使用隐式编码转换

mysql> 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;
​
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;
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表中查找条件匹配的行。explain的结果里面第二行的key=NULL表示的就是,这个过程是通过遍历主键索引的方式,一个一个地判断tradeid的值是否匹配。

进行到这里,你会发现第3步不符合我们的预期。因为表trade_detail里tradeid字段上是有索引的,我们本来是希望通过使用tradeid索引能够快速定位到等值的行。但,这里并没有。

如果你去问DBA同学,他们可能会告诉你,因为这两个表的字符集不同,一个是utf8,一个是utf8mb4,所以做表连接查询的时候用不上关联字段的索引。这个回答,也是通常你搜索这个问题时会得到的答案。

但是你应该再追问一下,为什么字符集不同就用不上索引呢?

我们说问题是出在执行步骤的第3步,如果单独把这一步改成SQL语句的话,那就是:

mysql> select * from trade_detail where tradeid=$L2.tradeid.value; 
​
其中,$L2.tradeid.value的字符集是utf8mb4。trade_detail的tradeid是utf8

优化:使用和索引列一样的编码类型,将编码转换放在输入参数上

mysql>select l.operator from tradelog l , trade_detail d where d.tradeid=l.tradeid and d.id=4;
 
这也是两个tradeid字段的join操作,为什么这次能用上被驱动表的tradeid索引呢?我们来分析一下。
​
假设驱动表trade_detail里id=4的行记为R4,那么在连接的时候(图5的第3步),被驱动表tradelog上执行的就是类似这样的SQL 语句:
​
select operator from tradelog  where traideid =$R4.tradeid.value; 
​
这时候$R4.tradeid.value的字符集是utf8, 按照字符集转换规则,要转成utf8mb4,所以这个过程就被改写成:
​
select operator from tradelog  where traideid =CONVERT($R4.tradeid.value USING utf8mb4); 
​
你看,这里的CONVERT函数是加在输入参数上的,这样就可以用上被驱动表的traideid索引。

总结一下上面的规律: 始终把索引列单独放在比较符号的另一侧。即不对索引列做任何函数操作。

全表扫描更快时(数据很少的时候)

原因是表中数据很少的时候,走索引还要做回表操作。

优化:可以使用强制使用索引或者强制忽略索引

select * from t force index(a) where a between 10000 and 20000;/*Q2*/SELECT * FROM table1 USE INDEX (col1_index,col2_index)
  WHERE col1=1 AND col2=2 AND col3=3;
​
SELECT * FROM table1 IGNORE INDEX (col3_index)
  WHERE col1=1 AND col2=2 AND col3=3;

使用OR关键字的查询

询语句的查询条件中只有OR关键字,且OR前后的两个条件中的列都是索引时,索引才会生效,否则,索引不生效。

select * from user where name = '2000' or pos ='cxy';
​
name列有索引 pos列无索引 索引会失效 
原因是 
    1.即使使用了name索引还是要回表
    2.由于pos列无索引,要走全表扫描
如果按上面的执行计划,那不如直接全表扫描判断2个条件是否成立

优化:使用union,2边都用索引

没有使用覆盖索引并且使用不等于(!=、<>)

!=不一定无法使用索引。

# mysql在使用不等于(!=<>)的时候无法使用索引会导致全表扫描(除覆盖索引外)
explain select * from user where age != 20;
explain select * from user where age <> 20;

优化:使用覆盖索引

is null,is not null可能无法使用索引

is null ,is not null走和不走索引是和数据量或者和其他元素有关系。(这里我只是测试到和数据量有关系) sql优化器在执行的时候会计算成本,其实和基数,选择性,直方图有关,其实就是看你所搜索的部分占全表的比例是走索引还是全表成本低。

总结:并不能一句话说 走和不走,需要看条件,比如数据量,等于“abc”的数据量和不等于“abc”的量,mysql在执行的时候会判断走索引的成本和全表扫描的成本,然后选择成本小的那个

www.cnblogs.com/fanBlog/p/1…

优化:尽量不要使用null作为默认值,给定一个具体的值做默认值。

联合索引的查询:order by 顺序不一致

优化:order by 保持顺序一致