什么时候使用B+树索引
并不是在所有的查询条件下出现的列都需要添加索引。对于什么时候添加B+树索引,我的经验是访问表中很少一部分时,使用B+树索引才有意义。对于性别字段,地区字段,类型字段,它们可取值的范围很小,即低选着性。如:
select * from student WHERE sex = 'M'
对于性别,可取值的范围只有'M','F'。对上述SQL语句得到的结果可能是该表的50%的数据,这时添加B+树索引时完全没有必要的。相反,如果某个字段的取值范围很广,几乎没有重复,即高选择性,即此时使用B+树索引时做合适的,例如姓名字段,基本上在一个应用中都不允许重名的出现。
因此,当访问高选择性字段并从表中取出很少一部分时,对这个字段添加B+树索引是非常有必要的。但是如果出现了访问字段是高选择性的,但是取出的行数据占用表中大部分的数据时,这时MySQL数据库就不会使用B+树索引了,我们先来看一个例子:
mysql> show index from info\G;
*************************** 1. row ***************************
Table: info
Non_unique: 0
Key_name: PRIMARY
Seq_in_index: 1
Column_name: id
Collation: A
Cardinality: 356639
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
*************************** 2. row ***************************
Table: info
Non_unique: 1
Key_name: index_link_family
Seq_in_index: 1
Column_name: link_family
Collation: A
Cardinality: 9385
Sub_part: 255
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
*************************** 3. row ***************************
Table: info
Non_unique: 1
Key_name: index_date
Seq_in_index: 1
Column_name: date
Collation: A
Cardinality: 356639
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
表info大约有50万行数据。info表上的date字段,该字段是日期类型,字段上有一个index_date的非唯一索引。我们来看下面两条SQL的执行:
mysql> explain select * from info where date = '2006-07-26 15:56:01'\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: info
type: ref
possible_keys: index_date
key: index_date
key_len: 8
ref: const
rows: 2
Extra:
1 row in set (0.00 sec)
ERROR:
No query specified
可以看到使用了index_date这个索引,这也符合我们前面提到的高选择性,选取表中很少行的原则。但是如果执行下面这条语句:
mysql> explain select * from info where date > '2006-07-26 15:56:01'\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: info
type: ALL
possible_keys: index_date
key: NULL
key_len: NULL
ref: NULL
rows: 356639
Extra: Using where
1 row in set (0.00 sec)
可以看到possible_keys依然是index_date,但是实际优化器使用的索引key显示的是NULL。为什么?因为这不符合我们前面说的原则,虽然date这个字段的值是高选择性的,但是我们取出的行占用了表中很大一部分。
mysql> select @a:=count(id) from info where date > '2006-07-26 15:56:01';
+---------------+
| @a:=count(id) |
+---------------+
| 452549 |
+---------------+
1 row in set (0.18 sec)
mysql> select @b:=count(id) from info ;
+---------------+
| @b:=count(id) |
+---------------+
| 452554 |
+---------------+
1 row in set (0.11 sec)
mysql> select @a/@b;
+--------+
| @a/@b |
+--------+
| 1.0000 |
+--------+
1 row in set (0.00 sec)
可以看到我们将取出行的数大概是表的100%的行,因此优化器没有选择使用索引。Mysql数据库的优化器会通过EXPLAIN的rows字段预估查询可能得到的行,如果大于某一个值,则B+树会选择全表的扫描。至于这个值,根据我的经验一般在20%。即当取出的数据量超过表中数据的20%,优化器就不会使用索引,而是进行全表的扫表。
但是预估的返回行数的值是不准确的,可以看到优化器判断日期小于2006-07-26的行为356639,而实际的是452549 。
有时优化器的选择并不完全是正确的,有时你更应该相信自己的判断(可以通过force index(index_name)来执行判断两条语句执行的时间差别)。