实践理解mysql的联合索引

B+树

mysql索引的数据结构最常见的是B+树。
在B+树中,所有数据记录都是放在同一层的叶子节点上,并且是按键值大小顺序存放的。
有序的存放比无序的存放,查询速度更快。
B+树的中间节点只存放指向下一层节点的指针,这样能让查询更快,叶子节点存储的数据更多。
B+树的高度一般都在2-4层,也就是说查找某一键值的行记录时,最多只需要2到4次IO。

联合索引

联合索引,是指将表上的多个列作为一个索引。

索引的底层是一颗B+树,联合索引也是一颗B+树,只不过联合索引的健值数量不是一个,而是多个。

注:图片出自《Mysql技术内幕InnoDB存储引擎》
可以看到,联合索引对应的键值(a,b),分别是 (1,1)、(1,2)、(2,1)、(2,4)、(3,1)、(3,2),
查询条件,我们按照a排序,明显是有序的 1、1、 2、2、3、3。
查询条件,我们按照(a,b)排序,也是有序的。a明显是有序,而当a值相同时,b值也是有序的。比如(1,1)和(1,2)
查询条件,我们按照b排序,就不是有序的了。1、2、1、4、1、2。

联合索引的结构,可以类比电话簿,人名由姓和名构成,
电话簿首先按姓氏对进行排序,然后按名字对有相同姓氏的人进行排序。
如果知道姓,电话簿是有序的;
如果知道姓和名,电话簿也是有序的,
但如果只知道名不姓,电话簿是无序的。
这个其实就是最左匹配原则。

最左匹配原则

联合索引(a,b,c),最左优先,从联合索引最左边的第一个字段进行查询,就会走联合索引,比如(a,b,c)、(a,b)或者(a,c)。
mysql的查询优化器会自动优化查询条件中的顺序,(b,a)相当于(a,b)。
在创建联合索引时,可以把查询比较频繁的查询条件放在最左边。
联合索引遇到范围查询(!=、>、<、between、or)就会停止匹配,不走联合索引。
如果第一个字段是范围查询,需要单独建一个索引。

示例

  • 创建数据表:

在表上创建联合索引 idx_order(order_id, user_id, pay_status)

CREATE TABLE t_index_test (                                                                                                                   
  id INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键,自增id',                                                                              
  order_id VARCHAR(25) NOT NULL COMMENT '订单号',
  user_id INT(11) NOT NULL COMMENT '用户id',                                                                                             
  pay_status TINYINT(1) DEFAULT 0 COMMENT '支付状态',                                                                                                                                                                                                                                   
  create_time TIMESTAMP  COMMENT '更新时间', 
  PRIMARY KEY (id),                                                                                 
  INDEX idx_order(order_id, user_id, pay_status)                                                                                                                      
) ENGINE=INNODB DEFAULT CHARSET=utf8   
  • 插入数据:
INSERT INTO t_index_test (order_id,user_id,pay_status)  VALUES('abc','123','2'); 
INSERT INTO t_index_test (order_id,user_id,pay_status)  VALUES('abc','456','2'); 
INSERT INTO t_index_test (order_id,user_id,pay_status)  VALUES('def','123','1');

EXPLAIN

首先,了解一下EXPLAIN, 它可以对 SELECT 语句进行分析,并输出 SELECT 执行的详细信息,方便针对性地优化。
EXPLAIN结果,重点看key这个字段,key表示此次查询中确切使用到的索引.

select_type: SELECT 查询的类型。包括SIMPLE、PRIMARY、UNION、UNION RESULT等
table: 查询的是哪个表
partitions: 匹配的分区
type: 类型。type值为all,表示全表扫描。type值为const,说明使用了主键索引。
不同的 type 类型的性能关系如下:
ALL < index < range ~ index_merge < ref < eq_ref < const < system。
possible_keys: 此次查询中可能选用的索引
key: 此次查询中确切使用到的索引.
ref: 哪个字段或常数与 key 一起被使用
rows: 显示此查询一共扫描了多少行. 这个是一个估计值.
filtered: 表示此查询条件所过滤的数据的百分比
extra: 额外的信息

联合索引的查询条件

  • 联合索引(a,b,c),查询条件为 (a,b,c)
EXPLAIN SELECT * FROM t_index_test  WHERE order_id='abc' AND user_id='123' AND pay_status='2'

EXPLAIN 结果: 查询条件为 (a,b,c)走索引。

    id  select_type  table         partitions  type    possible_keys  key        key_len  ref                  rows  filtered  Extra   
------  -----------  ------------  ----------  ------  -------------  ---------  -------  -----------------  ------  --------  --------
     1  SIMPLE       t_index_test  (NULL)      ref     idx_order      idx_order  83       const,const,const       1    100.00  (NULL)  
  • 联合索引(a,b,c),查询条件为 (a,b)
EXPLAIN SELECT * FROM t_index_test  WHERE order_id='abc' AND user_id='123'

EXPLAIN 结果: 查询条件为 (a,b)走索引。

    id  select_type  table         partitions  type    possible_keys  key        key_len  ref            rows  filtered  Extra   
------  -----------  ------------  ----------  ------  -------------  ---------  -------  -----------  ------  --------  --------
     1  SIMPLE       t_index_test  (NULL)      ref     idx_order      idx_order  81       const,const       1    100.00  (NULL)                                                                                                                                
  • 联合索引(a,b,c),查询条件为 (b,a)
EXPLAIN SELECT * FROM t_index_test  WHERE  user_id='123' AND order_id='abc' 

EXPLAIN 结果: 查询条件为 (b,a)走索引。 这是因为mysql会自动调节查询条件中的顺序,(b,a)相当于(a,b)

    id  select_type  table         partitions  type    possible_keys  key        key_len  ref            rows  filtered  Extra   
------  -----------  ------------  ----------  ------  -------------  ---------  -------  -----------  ------  --------  --------
     1  SIMPLE       t_index_test  (NULL)      ref     idx_order      idx_order  81       const,const       1    100.00  (NULL)  
  • 联合索引(a,b,c),查询条件为 (a,c)
EXPLAIN SELECT * FROM t_index_test  WHERE order_id='abc' AND pay_status='2'

EXPLAIN 结果: 查询条件为 (a,c)走索引。

    id  select_type  table         partitions  type    possible_keys  key        key_len  ref       rows  filtered  Extra                  
------  -----------  ------------  ----------  ------  -------------  ---------  -------  ------  ------  --------  -----------------------
     1  SIMPLE       t_index_test  (NULL)      ref     idx_order      idx_order  77       const        2     33.33  Using index condition  
  • 联合索引(a,b,c),查询条件为 (b,c)
EXPLAIN SELECT * FROM t_index_test  WHERE user_id='123' AND pay_status='2'

EXPLAIN 结果: 查询条件为 (b,c)不走索引

    id  select_type  table         partitions  type    possible_keys  key     key_len  ref       rows  filtered  Extra        
------  -----------  ------------  ----------  ------  -------------  ------  -------  ------  ------  --------  -------------
     1  SIMPLE       t_index_test  (NULL)      ALL     (NULL)         (NULL)  (NULL)   (NULL)       3     33.33  Using where  
  • 联合索引(a,b,c),查询条件为 (a!= , b, c)
EXPLAIN SELECT * FROM t_index_test  WHERE order_id!='abc' AND user_id='123' AND pay_status='2'

EXPLAIN 结果: 查询条件为 (a!= , b, c) 不走索引

    id  select_type  table         partitions  type    possible_keys  key     key_len  ref       rows  filtered  Extra        
------  -----------  ------------  ----------  ------  -------------  ------  -------  ------  ------  --------  -------------
     1  SIMPLE       t_index_test  (NULL)      ALL     idx_order      (NULL)  (NULL)   (NULL)       3     33.33  Using where  
  • 联合索引(a,b,c),查询条件为 (a , b!= , c)
EXPLAIN SELECT * FROM t_index_test  WHERE order_id='abc' AND user_id!='123' AND pay_status='2'

EXPLAIN 结果: 查询条件为 (a , b!=, c) 不走索引

    id  select_type  table         partitions  type    possible_keys  key     key_len  ref       rows  filtered  Extra        
------  -----------  ------------  ----------  ------  -------------  ------  -------  ------  ------  --------  -------------
     1  SIMPLE       t_index_test  (NULL)      ALL     idx_order      (NULL)  (NULL)   (NULL)       3     33.33  Using where  
  • 联合索引(a,b,c),查询条件为 (a OR b)
EXPLAIN SELECT * FROM t_index_test  WHERE order_id='abc' OR user_id='123' 

EXPLAIN 结果: 查询条件为 (a OR b) 不走索引

    id  select_type  table         partitions  type    possible_keys  key     key_len  ref       rows  filtered  Extra        
------  -----------  ------------  ----------  ------  -------------  ------  -------  ------  ------  --------  -------------
     1  SIMPLE       t_index_test  (NULL)      ALL     idx_order      (NULL)  (NULL)   (NULL)       3     55.56  Using where  

联合索引和多个单列索引的区别

  • 创建新的数据表,设置多个单独索引
CREATE TABLE t_index_test2 (                                                                                                                   
  id INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键,自增id',                                                                              
  order_id VARCHAR(25) NOT NULL COMMENT '订单号',
  user_id INT(11) NOT NULL COMMENT '用户id',                                                                                             
  pay_status TINYINT(1) DEFAULT 0 COMMENT '支付状态',                                                                                                                                                                                                                                   
  create_time TIMESTAMP  COMMENT '更新时间', 
  PRIMARY KEY (id),                                                                                 
  INDEX (order_id),                                                                                                                      
  INDEX (user_id),                                                                                                                      
  INDEX (pay_status)                                                                                                                      
) ENGINE=INNODB DEFAULT CHARSET=utf8  COMMENT '使用单列的索引' 
  • 多个单列索引,查询条件为 (a , b, c)
EXPLAIN SELECT * FROM t_index_test2  WHERE order_id='abc' AND user_id='123' AND pay_status='2'

EXPLAIN结果,走了单列索引的其中一个。
mysql优化器的优化策略,当多个查询条件时,mysql优化器会评估用哪个条件的索引效率最高,它会选择最佳的索引去使用。

    id  select_type  table          partitions  type    possible_keys                key       key_len  ref       rows  filtered  Extra        
------  -----------  -------------  ----------  ------  ---------------------------  --------  -------  ------  ------  --------  -------------
     1  SIMPLE       t_index_test2  (NULL)      ref     order_id,user_id,pay_status  order_id  77       const        1    100.00  Using where  
  • 多个单列索引,查询条件为 (a OR b)
EXPLAIN SELECT * FROM t_index_test2  WHERE order_id='abc' OR user_id='123' 

EXPLAIN结果,不走索引。

    id  select_type  table          partitions  type    possible_keys     key     key_len  ref       rows  filtered  Extra        
------  -----------  -------------  ----------  ------  ----------------  ------  -------  ------  ------  --------  -------------
     1  SIMPLE       t_index_test2  (NULL)      ALL     order_id,user_id  (NULL)  (NULL)   (NULL)       1    100.00  Using where  
  • 多个单列索引,查询条件为 (a!= , b)
EXPLAIN SELECT * FROM t_index_test2  WHERE order_id!='abc' AND user_id='123'

EXPLAIN结果,走了第二个单列索引。

    id  select_type  table          partitions  type    possible_keys     key      key_len  ref       rows  filtered  Extra        
------  -----------  -------------  ----------  ------  ----------------  -------  -------  ------  ------  --------  -------------
     1  SIMPLE       t_index_test2  (NULL)      ref     order_id,user_id  user_id  4        const        1    100.00  Using where  

联合索引和多个单列索引的区别: 联合索引有最左匹配原则,而多个单列索引没有。

结论:

联合索引(a,b,c),
查询条件为 (a,b,c)走索引,查询条件为 (a,b)走索引,查询条件为 (a,c)走索引,
查询条件为 (b,c)不走索引,查询条件为 (a!= , b, c) 不走索引,查询条件为 (a , b!=, c) 不走索引, 查询条件为 (a OR b) 不走索引。

参考资料:

《Mysql技术内幕InnoDB存储引擎》
https://blog.csdn.net/Abysscarry/article/details/80792876

原文地址:https://www.cnblogs.com/expiator/p/14873385.html