2013年10月28日星期一

关于子查询中的索引使用问题


为了测试mysql 使用子查询后的explain PRIMARY SQL 使用的不是真实的索引,做一个简单测试:

先看表结构:

mysql > desc employees;

+------------+---------------+------+-----+---------+-------+

| Field      | Type          | Null | Key | Default | Extra |

+------------+---------------+------+-----+---------+-------+

| emp_no     | int(11)       | NO   | PRI | NULL    |       |

| birth_date | date          | NO   |     | NULL    |       |

| first_name | varchar(14)   | NO   |     | NULL    |       |

| last_name  | varchar(16)   | NO   |     | NULL    |       |

| a          | enum('A')     | NO   |     | A       |       |

| T          | enum('A')     | YES  |     | A       |       |

| gender     | enum('M','F') | NO   |     | NULL    |       |

| hire_date  | date          | NO   |     | NULL    |       |

+------------+---------------+------+-----+---------+-------+

8 rows in set (0.00 sec)


为了和实际情况一致,我把 a,和 T 的字段类型都设置为 enum 类型。加两个索引:

mysql> alter table employees add index emp_no2(`birth_date`),add index emp_no3(`gender`);

mysql> show index from employees;
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table     | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| employees |          0 | PRIMARY  |            1 | emp_no      | A         |      300030 |     NULL | NULL   |      | BTREE      |         |               |
| employees |          1 | emp_no2  |            1 | birth_date  | A         |        9678 |     NULL | NULL   |      | BTREE      |         |               |
| employees |          1 | emp_no3  |            1 | gender      | A         |           2 |     NULL | NULL   |      | BTREE      |         |               |
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

通过desc(当然也可以explain) 查看当前sql 执行计划,从输出的结果来看,primary 表使用的是 emp_no3 也就是 gender 列为索引:

mysql> desc select emp_no from employees where emp_no in (select emp_no from salaries);
+----+--------------------+-----------+----------------+----------------+---------+---------+------+--------+--------------------------+
| id | select_type        | table     | type           | possible_keys  | key     | key_len | ref  | rows   | Extra                    |
+----+--------------------+-----------+----------------+----------------+---------+---------+------+--------+--------------------------+
|  1 | PRIMARY            | employees | index          | NULL           | emp_no3 | 1       | NULL | 300030 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | salaries  | index_subquery | PRIMARY,emp_no | emp_no  | 4       | func |      4 | Using index              |
+----+--------------------+-----------+----------------+----------------+---------+---------+------+--------+--------------------------+

我再次修改表的索引,使得看到的索引如下:

mysql> show index from employees;                                                 
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table     | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| employees |          0 | PRIMARY  |            1 | emp_no      | A         |      299654 |     NULL | NULL   |      | BTREE      |         |               |
| employees |          1 | emp_no2  |            1 | birth_date  | A         |        9666 |     NULL | NULL   |      | BTREE      |         |               |
| employees |          1 | emp_no3  |            1 | gender      | A         |           6 |     NULL | NULL   |      | BTREE      |         |               |
| employees |          1 | emp_no4  |            1 | T           | A         |           6 |     NULL | NULL   | YES  | BTREE      |         |               |
| employees |          1 | emp_no1  |            1 | a           | A         |           2 |     NULL | NULL   |      | BTREE      |         |               |
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

通过desc 查看当前sql 执行计划,从输出的结果来看,primary 表仍然使用的是 emp_no3 ,没有选择 emp_no4 或者 emp_no1 作为索引:

mysql> desc select emp_no from employees where emp_no in (select emp_no from salaries);                   
+----+--------------------+-----------+----------------+----------------+---------+---------+------+--------+--------------------------+
| id | select_type        | table     | type           | possible_keys  | key     | key_len | ref  | rows   | Extra                    |
+----+--------------------+-----------+----------------+----------------+---------+---------+------+--------+--------------------------+
|  1 | PRIMARY            | employees | index          | NULL           | emp_no3 | 1       | NULL | 299654 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | salaries  | index_subquery | PRIMARY,emp_no | emp_no  | 4       | func |      4 | Using index              |
+----+--------------------+-----------+----------------+----------------+---------+---------+------+--------+--------------------------+


难道同一类型的字段会和顺序有关吗?为了验证一下,我再次修改表,使得索引显示的顺序如下:

mysql> show index from employees;
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table     | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| employees |          0 | PRIMARY  |            1 | emp_no      | A         |      300600 |     NULL | NULL   |      | BTREE      |         |               |
| employees |          1 | emp_no2  |            1 | birth_date  | A         |       10020 |     NULL | NULL   |      | BTREE      |         |               |
| employees |          1 | emp_no4  |            1 | T           | A         |           6 |     NULL | NULL   | YES  | BTREE      |         |               |
| employees |          1 | emp_no1  |            1 | a           | A         |           6 |     NULL | NULL   |      | BTREE      |         |               |
| employees |          1 | emp_no3  |            1 | gender      | A         |           2 |     NULL | NULL   |      | BTREE      |         |               |
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

再次执行,我们看到当索引的位置顺序发生变化后,primary 表的key 变成了 emp_no1,那为什么不是emp_no4 呢? 原因是 T 这个字段未设置为 not null。

mysql> desc select emp_no from employees where emp_no in (select emp_no from salaries);                   
+----+--------------------+-----------+----------------+----------------+---------+---------+------+--------+--------------------------+
| id | select_type        | table     | type           | possible_keys  | key     | key_len | ref  | rows   | Extra                    |
+----+--------------------+-----------+----------------+----------------+---------+---------+------+--------+--------------------------+
|  1 | PRIMARY            | employees | index          | NULL           | emp_no1 | 1       | NULL | 301073 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | salaries  | index_subquery | PRIMARY,emp_no | emp_no  | 4       | func |      4 | Using index              |
+----+--------------------+-----------+----------------+----------------+---------+---------+------+--------+--------------------------+

执行 alter table employees change `T` `T` enum('A')  not null DEFAULT 'A';
修改表字段 T 为 not null 之后,使用的索引就变成 emp_no4 了

mysql> show index from employees;                        
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table     | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| employees |          0 | PRIMARY  |            1 | emp_no      | A         |      299917 |     NULL | NULL   |      | BTREE      |         |               |
| employees |          1 | emp_no2  |            1 | birth_date  | A         |        9997 |     NULL | NULL   |      | BTREE      |         |               |
| employees |          1 | emp_no4  |            1 | T           | A         |           6 |     NULL | NULL   |      | BTREE      |         |               |
| employees |          1 | emp_no1  |            1 | a           | A         |           6 |     NULL | NULL   |      | BTREE      |         |               |
| employees |          1 | emp_no3  |            1 | gender      | A         |          54 |     NULL | NULL   |      | BTREE      |         |               |
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+


mysql> desc select emp_no from employees where emp_no in (select emp_no from salaries);
+----+--------------------+-----------+----------------+----------------+---------+---------+------+--------+--------------------------+
| id | select_type        | table     | type           | possible_keys  | key     | key_len | ref  | rows   | Extra                    |
+----+--------------------+-----------+----------------+----------------+---------+---------+------+--------+--------------------------+
|  1 | PRIMARY            | employees | index          | NULL           | emp_no4 | 1       | NULL | 299917 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | salaries  | index_subquery | PRIMARY,emp_no | emp_no  | 4       | func |      4 | Using index              |
+----+--------------------+-----------+----------------+----------------+---------+---------+------+--------+--------------------------+
2 rows in set (0.01 sec)

最后,我们来看看会不会用到 emp_no2 呢?我们把 emp_no1,emp_no3,emp_no4 都删除,只保留 emp_no2 和primary key 的 emp_no。

mysql> alter table employees drop index emp_no1, drop index emp_no3,drop index emp_no4;

mysql> show index from employees;                                            
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table     | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| employees |          0 | PRIMARY  |            1 | emp_no      | A         |      299917 |     NULL | NULL   |      | BTREE      |         |               |
| employees |          1 | emp_no2  |            1 | birth_date  | A         |        9997 |     NULL | NULL   |      | BTREE      |         |               |
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

mysql> desc select emp_no from employees where emp_no in (select emp_no from salaries);
+----+--------------------+-----------+----------------+----------------+---------+---------+------+--------+--------------------------+
| id | select_type        | table     | type           | possible_keys  | key     | key_len | ref  | rows   | Extra                    |
+----+--------------------+-----------+----------------+----------------+---------+---------+------+--------+--------------------------+
|  1 | PRIMARY            | employees | index          | NULL           | emp_no2 | 3       | NULL | 300718 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | salaries  | index_subquery | PRIMARY,emp_no | emp_no  | 4       | func |      4 | Using index              |
+----+--------------------+-----------+----------------+----------------+---------+---------+------+--------+--------------------------+





没有评论:

发表评论