什么时候使用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)来执行判断两条语句执行的时间差别)。