mysql多列索引的建立和优化

文章目录


对于单列索引,没有太多的话题,但是对于多列索引的建立,一个好的多列索引能使用的场景应可以涵盖很多,减少其他不必要的索引空间,就有很多事情需要注意。

0.首先来了解索引的物理结构:

http://www.jianshu.com/p/1775b4ff123a

1.where 子句中的多列索引

如果表有多个列索引,可以使用任何左边的前缀索引的优化器来查找行。如何通过前缀索引来查找行我们通过如下例子来详细了解:

现有表formatting,数据如下图所示:
在这里插入图片描述

在该表上建立多列索引:

create index idx_custid_qty_empid on formatting(custid,qty,empid)

1.1 完全使用索引的情况

例如,如果你有一个三列的索引(col1、col2 col3),有索引搜索功能(col1),(col1,col2),(col1、col2、col3)。

1)where中条件只有col1等于常量

说明:表中custid为A的数据共4行,这4行数据都使用了索引。

mysql> explain select orderid,orderdate from formatting where custid='A'  \G 
*************************** 1. row *************************** 
id: 1 
select_type: SIMPLE 
table: formatting 
partitions: NULL
type: ref 
possible_keys: idx_custid_qty_empid 
key: idx_custid_qty_empid 
key_len: 32 
ref: const 
rows: 4 filtered: 100.00 
Extra: Using index

2)where中条件只有col1为范围(>,<,>=,<=)

说明:表中custid大于A的有5行,这5行数据都使用了索引。

mysql> explain select orderid,orderdate from formatting where custid>'A'  \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: formatting
partitions: NULL
type: range
possible_keys: idx_custid_qty_empid
key: idx_custid_qty_empid
key_len: 32
ref: NULL
rows: 5
filtered: 100.00
Extra: Using where; Using index

3)where中条件有col1,col2,且col1等于常量,col2等于常量

mysql> explain select orderid,orderdate from formatting where custid='A' and qty=20  \G 
*************************** 1. row ***************************
id: 1 
select_type: SIMPLE 
table: formatting 
partitions: NULL 
type: ref 
possible_keys: idx_custid_qty_empid 
key: idx_custid_qty_empid 
key_len: 36 
ref: const,const 
rows: 1 
filtered: 100.00 
Extra: Using index

表中custid为A,qty等于20的数据只有一条,两个字段都使用了索引。

4)where中条件有col1,col2,且col1等于常量,col2等于范围

mysql> explain select orderid,orderdate from formatting where custid='A' and qty>=20  \G 
*************************** 1. row *************************** 
id: 1 
select_type: SIMPLE 
table: formatting 
partitions: NULL 
type: range 
possible_keys: idx_custid_qty_empid 
key: idx_custid_qty_empid 
key_len: 36 
ref: NULL rows: 2 
filtered: 100.00 
Extra: Using where; Using index

表中custid为A,qty大于等于20的数据有2条,两个字段都使用了索引。

5)where中条件有col1,col2,col3,且col1,col2,col3均等于常量

说明:表中custid为A,qty等于10,empid等于2的数据有1条。3个字段都使用了索引。

mysql> explain select orderid,orderdate from formatting where custid='A' and qty=10 and empid=2  \G 
*************************** 1. row *************************** 
id: 1 
select_type: SIMPLE 
table: formatting 
partitions: NULL 
type: ref 
possible_keys: idx_custid_qty_empid 
key: idx_custid_qty_empid 
key_len: 40 
ref: const,const,const 
rows: 1 
filtered: 100.00 
Extra: Using index

6)where中条件有col1,col2,col3,且col1,col2均等于常量,col3为范围

说明:表中 custid 为 A,qty 等于 10,empid 大于 2 的数据有1条,这3个字段都使用了索引。

mysql> explain select orderid,orderdate from formatting where custid='A' and qty=10 and empid>2   \G 
*************************** 1. row *************************** 
id: 1 
select_type: SIMPLE 
table: formatting 
partitions: NULL 
type: range 
possible_keys: idx_custid_qty_empid 
key: idx_custid_qty_empid 
key_len: 40 
ref: NULL 
rows: 1 
filtered: 100.00 
Extra: Using where; Using index

1.2 部分使用索引的情况

1)where中条件col1等于常量,无col2,col3的常量或范围,只能用到col1的索引

mysql> explain select orderid,orderdate from formatting where custid='A' and empid=2\G
 *************************** 1. row ***************************
 id: 1 
 select_type: SIMPLE 
 table: formatting 
 partitions: NULL
 type: ref 
 possible_keys: idx_custid_qty_empid 
 key: idx_custid_qty_empid 
 key_len: 32 ref: const 
 rows: 4 filtered: 11.11 
 Extra: Using where; Using index

说明:custid='A' and empid=2 的数据仅有一条。由于联合索引的第2例缺失,导致第3列 empid=2 的索引不生效,仅第1列的 custid='A' 的4条数据使用了索引优化。

2)where中条件col1等于常量,col2的范围,col3的常量或范围,只能用到col1、col2的索引

mysql> explain select orderid,orderdate from formatting where custid='A' and qty<20 and empid=2  \G 
*************************** 1. row *************************** 
id: 1 
select_type: SIMPLE 
table: formatting 
partitions: NULL 
type: range 
possible_keys: idx_custid_qty_empid 
key: idx_custid_qty_empid 
key_len: 36 
ref: NULL rows: 2 
filtered: 11.11 
Extra: Using where; Using index

custid='A' and qty<20 and empid=2 的结果只有一条。
由于第2列 qty<20 为范围,导致第3列empid=2 索引失效。仅第1、2列的 custid='A' and qty<20 使用索引。

3)where中条件col1的范围,col2、col3的常量或范围,只能用到col1的索引

mysql> explain select orderid,orderdate from formatting where custid>'A' and empid=2 \G 
*************************** 1. row ***************************
id: 1 
select_type: SIMPLE 
table: formatting 
partitions: NULL 
type: range 
possible_keys: idx_custid_qty_empid 
key: idx_custid_qty_empid 
key_len: 32 
ref: NULL 
rows: 5 
filtered: 11.11 
Extra: Using where; Using index

custid>'A' and empid=2 的数据仅有一条。
由于 custid>'A' 为范围查询,故仅第1列 custid>'A' 的5条数据使用索引 ,第2、3列的索引失效。

1.3 索引的失效

下列情况均需要扫描所有行再进行筛选,因为多列索引的 第1列没有在where条件 中,后面列的索引全部失效。

  • 1)col2 常量或范围;
  • 2)col3 常量或范围;
  • 3)col2、col3 常量或范围;

说明:由于没有 custid 的条件需要扫描9行数据。为何下面的例子中,却使用了type: index?

因为:当索引是覆盖索引并且可以用来满足需要从表中得到的所有数据时,只有索引树被扫描。在这种情况下,(explain 命令)返回记录里的Extra字段提示 Using index。此表中的 orderid,orderdate为联合主键。

mysql> explain select orderid,orderdate from formatting where qty=20  \G 
*************************** 1. row *************************** 
id: 1 
select_type: SIMPLE 
table: formatting 
partitions: NULL 
type: index 
possible_keys: NULL 
key: idx_custid_qty_empid 
key_len: 40 
ref: NULL 
rows: 9 
filtered: 11.11 
Extra: Using where; Using index

我们添加一列新的列名叫version,并对其赋值,如下图所示。

在这里插入图片描述

再来看一下上面会发生什么变化,此时要查询多列避免出现覆盖索引,我们发现没有使用任何索引了:

mysql> desc select orderid,orderdate,empid,custid,qty,version from formatting where qty=20 \G
 *************************** 1. row *************************** 
 id: 1 
 select_type: SIMPLE 
 table: formatting 
 partitions: NULL 
 type: ALL 
 possible_keys: NULL 
 key: NULL 
 key_len: NULL 
 ref: NULL 
 rows: 10 
 filtered: 10.00 
 Extra: Using where

2、order by 子句中的多列索引

2.1、order by 的字段满足索引条件,则使用索引。

1) order by 子句中的字段顺序与索引字段顺序一致

create index idx_custid_qty on formatting(custid,qty);

mysql> desc select orderid from formatting order by custid,qty  \G 
*************************** 1. row *************************** 
id: 1 
select_type: SIMPLE 
table: formatting 
partitions: NULL 
type: index 
possible_keys: NULL 
key: idx_custid_qty 
key_len: 36 
ref: NULL 
rows: 10 
filtered: 100.00 
Extra: Using index

注意:此时若使用 select *, 则不会使用索引。

mysql> desc select * from formatting order by custid,qty \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: formatting
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 9
filtered: 100.00
Extra: Using filesort

那么为何有索引却不能使用呢,我们结合最初的链接中的索引物理结构来理解:

第一种方法是直接读取表记录,然后根据 order by 列去排序。
第二种方法是先扫描 order by 上的索引,由于索引本身就是排好序的,因此根据索引的顺序再读出表的记录即可,省去排序的过程。

我们可以看出第一种方法直接读取表记录就可以了。第二种方法需要先读取索引,然后根据索引去读取表记录,io会变得很随机,因此这种方法会成本比较高,所以优化器会选择第一种执行执行划。

2)按照索引顺序的where和order by子句可以使用,但where条件中必须使用字段名(> < =)某常量

mysql> desc select * from formatting where custid='A' order by qty \G
*************************** 1. row ***************************
id: 1 
select_type: SIMPLE 
table: formatting 
partitions: NULL 
type: ref 
possible_keys: idx_custid_qty 
key: idx_custid_qty 
key_len: 32 
ref: const 
rows: 4 
filtered: 100.00 
Extra: Using index condition

custid、qty 都使用了索引。

2.2、下列情况,order by 索引失效

1)同一个order by 中的字段有多个索引

# 建立 custid 和 empid 的两个单独索引
create index idx_custid on formatting(custid) ;
create index idx_empid on formatting(empid) ;

mysql> desc select orderdate from formatting order by custid,empid \G 
*************************** 1. row *************************** 
id: 1 
select_type: SIMPLE 
table: formatting 
partitions: NULL 
type: ALL 
possible_keys: NULL 
key: NULL 
key_len: NULL 
ref: NULL 
rows: 10 
filtered: 100.00 
Extra: Using filesort

上例的例子中,order by custid,empid 使用了 idx_custididx_empid 两个索引,结果两个索引都失效。

2)where 与order by 字段的顺序与索引顺序不同,排序索引失效

# 联合索引
create index idx_custid_qty_empid on formatting(custid,qty,empid)
# 单列索引
create index idx_qty on formatting(qty)

mysql> desc select orderdate from formatting where qty=10 order by custid,empid  \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: formatting
partitions: NULL
type: ref
possible_keys: idx_qty
key: idx_qty
  key_len: 4
      ref: const
     rows: 2
 filtered: 100.00
    Extra: Using index condition; Using filesort

例子中联合索引没有生效,只有 where 中的单个索引生效了。

3) 排序同时包含 asc 与 desc,则排序索引不生效,排序 Using filesort,只有 where 条件使用索引。

# 删除其他索引
.....

##创建索引
create index idx_custid_qty_empid on formatting(custid,qty,empid);

mysql> explain select orderid,orderdate,empid,custid,qty,version from formatting where custid='A' order by qty desc,empid asc  \G 
*************************** 1. row *************************** 
id: 1 
select_type: SIMPLE 
table: formatting 
partitions: NULL 
type: ref 
possible_keys: idx_custid_qty_empid 
key: idx_custid_qty_empid 
key_len: 32 
ref: const 
rows: 4 
filtered: 100.00 
Extra: Using index condition; Using filesort

上例中,只有 where 条件使用索引,因为order by同时包含 asc 与 desc,则排序的索引没有生效,但是 排序 使用了 Using filesort

4) order by 字段中有计算,排序不能使用索引

先来看不使用计算时,可以使用索引的情况:

mysql> explain select orderid,orderdate,empid,custid,qty,version from formatting where custid='A' order by qty \G 
*************************** 1. row *************************** 
id: 1 
select_type: SIMPLE 
table: formatting 
partitions: NULL 
type: ref 
possible_keys: idx_custid_qty_empid 
key: idx_custid_qty_empid 
key_len: 32 
ref: const 
rows: 4 
filtered: 100.00 
Extra: Using index condition

增加计算后,排序索引(order by qty+1)失效了,只使用了 Using filesort。

mysql> explain select orderid,orderdate,empid,custid,qty,version from formatting where custid='A' order by qty+1 \G
*************************** 1. row *************************** 
id: 1 
select_type: SIMPLE 
table: formatting 
partitions: NULL 
type: ref 
possible_keys: idx_custid_qty_empid 
key: idx_custid_qty_empid 
key_len: 32 
ref: const 
rows: 4 
filtered: 100.00 
Extra: Using index condition; Using filesort

5)order by 和 group by 的表达式不同,排序索引失效

先来看下order by 和 group by 的表达式相同情况,则不需要Using filesort。

mysql> explain select custid,qty from formatting group by custid,qty order by custid,qty \G

*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: formatting
partitions: NULL
type: index
possible_keys: idx_custid_qty_empid
key: idx_custid_qty_empid
key_len: 40
ref: NULL
rows: 10
filtered: 100.00
Extra: Using index

但是,如果不同的情况排序时,索引失效,需要再一次Using filesort。

mysql> explain select custid,qty from formatting group by custid,qty order by qty \G *************************** 1. row *************************** 
id: 1 
select_type: SIMPLE 
table: formatting 
partitions: NULL 
type: index 
possible_keys: idx_custid_qty_empid 
key: idx_custid_qty_empid 
key_len: 40 
ref: NULL 
rows: 10 
filtered: 100.00 
Extra: Using index; Using temporary; Using filesort

6) join 的表 order by 字段来自不同的表,只能使用主表的索引

如果查询需要关联多张表,则只有当 order by 子句引用的字段全部为第一个表时,才能使用索引做排序。此时需要注意,在 inner join 时,优化器将哪张表选做主表,就只能使用此表的索引。

其中一定要满足order by 在单表查询中的要求,若去掉 T1.empid=2 ,则同2.1 中1情况不能使用排序索引。

使用主表的排序:

mysql> desc select * from formatting t1 left join emp t2 on t1.empid=t2.empid WHERE T1.empid=2 order by T1.custid \G 
*************************** 1. row *************************** 
id: 1 
select_type: SIMPLE
table: t1 
partitions: NULL 
type: ref 
possible_keys: idx_empid_custid 
key: idx_empid_custid 
key_len: 4 
ref: const 
rows: 2 
filtered: 100.00 
Extra: Using index condition 

*************************** 2. row *************************** 
id: 1 
select_type: SIMPLE 
table: t2 
partitions: NULL
type: ref 
possible_keys: idx_empid
key: idx_empid 
key_len: 5 
ref: const 
rows: 1 
filtered: 100.00 
Extra: NULL

使用第二张表排序,需要 Using filesort:

mysql> desc select * from formatting t1 left join emp t2 on t1.empid=t2.empid WHERE T1.empid=2 order by T2.empname \G 
*************************** 1. row *************************** 
id: 1 
select_type: SIMPLE 
table: t1 
partitions: NULL 
type: ref 
possible_keys: idx_empid_custid 
key: idx_empid_custid 
key_len: 4 
ref: const 
rows: 2 
filtered: 100.00 
Extra: Using temporary; Using filesort 

*************************** 2. row *************************** 
id: 1 
select_type: SIMPLE 
table: t2 
partitions: NULL 
type: ref 
possible_keys: idx_empid 
key: idx_empid 
key_len: 5 
ref: const 
rows: 1 
filtered: 100.00 
Extra: NULL

对于上述去掉where条件情况可以如下优化语句:
优化前语句:

select * from a left join b on a.id=b.a_id order a.id desc

优化后语句:

select * 
from a 
left join b on a.id=b.a_id 
join (select id from a order by id desc) a_order
 on a.id = a_order.id

猜你喜欢

转载自blog.csdn.net/xiaojin21cen/article/details/84325996