1. 独立的列
MysqL> select * from customer where id + = @H_502_37@;
MysqL> select * from customer where to_days@H_502_37@(birth_date@H_502_37@) - to_days@H_502_37@('2020-06-07'@H_502_37@) <= @H_502_37@;
2. 前缀索引
有时候需要对很长的字符列创建索引,这会使得索引变得很占空间,效率也很低下。碰到这种情况,一般可以索引开始的部分字符,这样可以节省索引产生的空间,但同时也会降低索引的选择性。
那我们就要选择足够长的前缀来保证较高的选择性,但是为了节省空间,前缀又不能太长,只要前缀的基数,接近于完整列的基数即可。
Tips:索引的选择性指,不重复的索引值(也叫基数,cardinality)和数据表的记录总数的比值,索引的选择性越高表示查询效率越高。
完整列的选择性:
MysqL> select count@H_502_37@(distinct last_name@H_502_37@)/count@H_502_37@(*@H_502_37@) from customer@H_502_37@;
+------------------------------------+
| count@H_502_37@(distinct last_name@H_502_37@)/count@H_502_37@(*@H_502_37@) |
+------------------------------------+
| |
+------------------------------------+
不同前缀长度的选择性:
MysqL> select count@H_502_37@(distinct left@H_502_37@(last_name@H_502_37@,@H_502_37@)@H_502_37@)/count@H_502_37@(*@H_502_37@) left_3@H_502_37@, count@H_502_37@(distinct left@H_502_37@(last_name@H_502_37@,@H_502_37@)@H_502_37@)/count@H_502_37@(*@H_502_37@) left_4@H_502_37@, count@H_502_37@(distinct left@H_502_37@(last_name@H_502_37@,@H_502_37@)@H_502_37@)/count@H_502_37@(*@H_502_37@) left_5@H_502_37@, count@H_502_37@(distinct left@H_502_37@(last_name@H_502_37@,@H_502_37@)@H_502_37@)/count@H_502_37@(*@H_502_37@) left_6 from customer@H_502_37@;
+--------+--------+--------+--------+
| left_3 | left_4 | left_5 | left_6 |
+--------+--------+--------+--------+
| | | | |
+--------+--------+--------+--------+
创建前缀长度为6的索引:
MysqL> alter table customer add index idx_last_name@H_502_37@(last_name@H_502_37@(@H_502_37@)@H_502_37@)@H_502_37@;
前缀索引可以使索引更小更快,但同时也有缺点:无法使用前缀索引做 order by 和 group by,也无法使用前缀索引做覆盖扫描。
3. 合适的索引列顺序
在一个多列 B-Tree 索引中,索引列的顺序表示索引首先要按照最左列进行排序,然后是第二列、第三列等。索引可以按照升序或降序进行扫描,以满足精确符合列顺序的 order by、group by 和 distinct 等的查询需求。
索引的列顺序非常重要,在不考虑排序和分组的情况下,通常我们会将选择性最高的列放到索引最前面。
我们首先来计算下这两个列的选择性,看哪个列更高。
MysqL> select count@H_502_37@(distinct last_name@H_502_37@)/count@H_502_37@(*@H_502_37@) last_name_selectivity@H_502_37@, count@H_502_37@(distinct first_name@H_502_37@)/count@H_502_37@(*@H_502_37@) first_name_selectivity from customer@H_502_37@;
+-----------------------+------------------------+
| last_name_selectivity | first_name_selectivity |
+-----------------------+------------------------+
| | |
+-----------------------+------------------------+
MysqL> alter table customer add index idx1_customer@H_502_37@(first_name@H_502_37@,last_name@H_502_37@)@H_502_37@;
4. 覆盖索引
如果一个索引包含所有需要查询的字段,称之为覆盖索引。由于覆盖索引无须回表,通过扫描索引即可拿到所有的值,它能极大地提高查询效率:索引条目一般比数据行小的多,只通过扫描索引即可满足查询需求,MysqL 可以极大地减少数据的访问量。
MysqL> explain select last_name@H_502_37@, first_name from customer\G
*************************** @H_502_37@. row ***************************
id:
select_type: SIMPLE
table: customer
partitions: NULL
type: index
possible_keys: NULL
key: idx1_customer
key_len:
ref: NULL
rows:
filtered:
Extra: Using index
row in set@H_502_37@, warning @H_502_37@( sec@H_502_37@)
5. 使用索引实现排序
order by 和查询的限制是一样的,需要满足索引的最左前缀要求,否则无法使用索引进行排序。只有当索引的列顺序和 order by 子句的顺序完全一致,并且所有列的排序方向(正序或倒序)都一致,MysqL才能使用索引来做排序。如果查询是多表关联,只有当 order by 子句引用的字段全部为第一个表时,才能使用索引来做排序。
以表 customer 为例,我们来看看哪些查询可以通过索引进行排序。
MysqL> create table customer@H_502_37@(
id int@H_502_37@,
last_name varchar@H_502_37@(@H_502_37@)@H_502_37@,
first_name varchar@H_502_37@(@H_502_37@)@H_502_37@,
birth_date date@H_502_37@,
gender char@H_502_37@(@H_502_37@)@H_502_37@,
key idx_customer@H_502_37@(last_name@H_502_37@,first_name@H_502_37@,birth_date@H_502_37@)
@H_502_37@)@H_502_37@;
5.1 可以通过索引进行排序的查询
索引的列顺序和 order by 子句的顺序完全一致:
MysqL> explain select last_name@H_502_37@,first_name from customer order by last_name@H_502_37@, first_name@H_502_37@, birth_date\G
*************************** @H_502_37@. row ***************************
id:
select_type: SIMPLE
table: customer
partitions: NULL
type: index
possible_keys: NULL
key: idx_customer
key_len:
ref: NULL
rows:
filtered:
Extra: Using index
row in set@H_502_37@, warning @H_502_37@( sec@H_502_37@)
索引的第一列指定为常量:
从 explain 可以看到没有出现排序操作(filesort):
MysqL> explain select * from customer where last_name = 'Allen' order by first_name@H_502_37@, birth_date\G
*************************** @H_502_37@. row ***************************
id:
select_type: SIMPLE
table: customer
partitions: NULL
type: ref
possible_keys: idx_customer
key: idx_customer
key_len:
ref: const
rows:
filtered:
Extra: Using index condition
row in set@H_502_37@, warning @H_502_37@( sec@H_502_37@)
索引的第一列指定为常量,使用第二列排序:
MysqL> explain select * from customer where last_name = 'Allen' order by first_name desc\G
*************************** @H_502_37@. row ***************************
id:
select_type: SIMPLE
table: customer
partitions: NULL
type: ref
possible_keys: idx_customer
key: idx_customer
key_len:
ref: const
rows:
filtered:
Extra: Using where
row in set@H_502_37@, warning @H_502_37@( sec@H_502_37@)
索引的第一列为范围查询,order by 使用的两列为索引的最左前缀:
MysqL> explain select * from customer where last_name between 'Allen' and 'Bush' order by last_name@H_502_37@,first_name\G
*************************** @H_502_37@. row ***************************
id:
select_type: SIMPLE
table: customer
partitions: NULL
type: range
possible_keys: idx_customer
key: idx_customer
key_len:
ref: NULL
rows:
filtered:
Extra: Using index condition
row in set@H_502_37@, warning @H_502_37@( sec@H_502_37@)
5.2 不能通过索引进行排序的查询
使用两种不同的排序方向:
MysqL> explain select * from customer where last_name = 'Allen' order by first_name desc@H_502_37@, birth_date asc\G
*************************** @H_502_37@. row ***************************
id:
select_type: SIMPLE
table: customer
partitions: NULL
type: ref
possible_keys: idx_customer
key: idx_customer
key_len:
ref: const
rows:
filtered:
Extra: Using index condition@H_502_37@; Using filesort
row in set@H_502_37@, warning @H_502_37@( sec@H_502_37@)
order by 子句引用了一个不在索引的列:
MysqL> explain select * from customer where last_name = 'Allen' order by first_name@H_502_37@, gender\G
*************************** @H_502_37@. row ***************************
id:
select_type: SIMPLE
table: customer
partitions: NULL
type: ref
possible_keys: idx_customer
key: idx_customer
key_len:
ref: const
rows:
filtered:
Extra: Using index condition@H_502_37@; Using filesort
row in set@H_502_37@, warning @H_502_37@( sec@H_502_37@)
where 条件和 order by 的列无法组成索引的最左前缀:
MysqL> explain select * from customer where last_name = 'Allen' order by birth_date\G
第一列是范围查询,where 条件和 order by 的列无法组成索引的最左前缀:
MysqL> explain select * from customer where last_name between 'Allen' and 'Bush' order by first_name\G
MysqL> explain select * from customer where last_name = 'Allen' and first_name in @H_502_37@('Cuba'@H_502_37@,'Kim'@H_502_37@) order by birth_date\G