为了实现区间查询、减少 IO 次数,MySQL InnoDB 的索引由 B+Tree 实现。
索引组织表
InnoDB 是以索引组织表,即为每张表的每个索引创建一棵树。
- 聚簇(主键)索引树
- 非叶子结点中存放主键或 row_id(MySQL 为每条记录生成的唯一id)
- 叶子结点中存放数据
- 非簇聚索引(二级索引)树
- 非叶子结点中存放二级索引信息
- 叶子结点中存放主键信息
回表
查询语句命中二级索引后,通过二级索引中存储的主键信息回主键索引树中查找具体数据。这个过程称之 “回表”。最左前缀
命中二级索引时,可只命中最左的 N 个字段或字符串索引的最左 M 个字符;``sql create table
test` ( id int primary key, guid varchar(36) not null default ‘’, name varchar(10) not null default ‘’, city varchar(10) not null default ‘’, index idx_guid_name(guid, name)) engine=InnoDB;
select from test where guid=’abc’ and name=’张三’; — 命中索引 select from test where guid=’abc’; — 命中索引 select from test where guid like ‘abc%’; — 命中索引 select from test where guid like ‘%abc’; — 未命中索引 select * from test where name=’张三’; — 未命中索引
<a name="G5Q6V"></a>
### 覆盖索引
查询语句命中二级索引后,若查询的所有字段均在二级索引中,则不需要回表。
```sql
create table `test` (
id int primary key,
guid varchar(36) not null default '',
name varchar(10) not null default '',
index idx_guid_name(guid, name))
engine=InnoDB;
select name from test where guid='abc'; -- 命中索引且 name 字段存在索引中,不需要回表
select id from test where guid='abc'; -- 命中索引但 id 字段不在索引中,需要回表
索引下推
MySQL version >= 5.6
查询语句命中二级索引后,若后续查询字段存在二级索引中,则不需要回表判断。
create table `test` (
id int primary key,
guid varchar(36) not null default '',
name varchar(10) not null default '',
city varchar(10) not null default '',
index idx_guid_name(guid, name))
engine=InnoDB;
-- 命中索引
-- city 字段不在索引中,需回表判断
select * from test where guid='abc' and city='北京';
-- 根据最左前缀原则只有 guid 命中索引
-- name 字段存在索引中,所以不需要回表判断,可直接在二级索引树中判断
select * from test where guid like 'abc%' and name='张三';
唯一索引还是普通索引?
前提:
- 一个 SQL 语句命中数据后,若数据所在数据页不在内存中,会先将该数据页加载至内存中在进行后续操作。
- 数据页被加载到内存中时,先执行
change buffer
内记录的 sql,然后在进行当前操作。
唯一索引
UPDATE
语句命中索引- 数据页已在内存中,直接修改数据
-
普通索引
UPDATE
语句命中索引- 数据页已在内存中,直接修改数据
- 数据页不在内存中,将当前语句写入
change buffer
UPDATE
结束
综上所述,唯一索引适合读多写少的场景,普通索引适合写多读少的场景。