在查询中,虽然 col2 是联合索引的第二列,但是索引的第一个列 col1 没有用到,索引依然可能被扫描,但它不能有效地过滤掉 col1 和 col3 的范围,因此可能需要全表扫描或者通过回表来补充数据。举例说明一下
在数据库中,“回表”是指查询中所需要的列不在索引中时,数据库必须回到原始数据表(即“主表”)去获取这些列的值,通常是通过“主键”或者“唯一索引”来进行查找。回表会导致查询性能下降,因为数据库需要额外的访问原始数据表。
### 回表的原因和原理
当你使用索引进行查询时,索引通常只包含一部分列的数据(索引列)。如果查询涉及的列不在索引中,数据库就需要通过索引中的值去回到原始数据表,查找完整的行数据。这就是“回表”操作。
### 举个例子
假设你有一个表 `students`,该表有三个列:`student_id`(主键),`name`,`age`。
并且你创建了如下联合索引:
```sql
CREATE INDEX idx_name_age ON students(name, age);
```
那么,`idx_name_age` 索引包含了 `name` 和 `age` 列,但不包括 `student_id` 列。
#### 例子 1:不需要回表的查询
```sql
SELECT name, age FROM students WHERE name = 'Alice';
```
这个查询只涉及 `name` 和 `age` 列,而这两列都在索引 `idx_name_age` 中。因此,查询可以直接通过索引来获得查询结果,无需回表。
#### 例子 2:需要回表的查询
```sql
SELECT student_id, name FROM students WHERE name = 'Alice';
```
这个查询需要返回 `student_id` 和 `name`,但是由于 `student_id` 不在 `idx_name_age` 索引中,所以数据库会先通过索引扫描 `name` 列来找到符合条件的记录。假设查询返回了 `name = 'Alice'` 的多行数据,但由于索引中并没有包含 `student_id`,所以数据库必须回到原始数据表中,通过索引找到对应的 `student_id` 列。这就需要一次额外的磁盘访问或内存访问。
这种额外的访问被称为“回表”,会导致查询效率降低,尤其是在查询大量数据时,回表操作可能会显著增加查询的延迟。
### 何时发生回表
- **覆盖索引**:如果查询只涉及索引中的列,那么不需要回表。例如,如果你创建了一个覆盖索引(包含查询所需的所有列),则不需要回表,索引可以直接返回完整的结果集。
- **非覆盖索引**:如果查询涉及的列不在索引中,或者查询中有更多列需要从表中获取,就会发生回表操作。
### 回表的影响
回表操作增加了数据库访问的数据量,因为它需要额外的 I/O 操作来从数据表中获取非索引列的数据。如果查询只需要返回部分列,而这些列不在索引中,就会引入额外的开销,尤其是在表中的数据量较大时,性能差距可能会非常明显。
### 示例总结
- **无回表的查询:**
```sql
SELECT name, age FROM students WHERE name = 'Alice';
```
- 直接通过索引获取结果,无需回表。
- **有回表的查询:**
```sql
SELECT student_id, name FROM students WHERE name = 'Alice';
```
- 使用索引定位到符合 `name = 'Alice'` 的记录,但由于 `student_id` 不在索引中,需要回表来查找 `student_id`,增加了 I/O 操作。
### 如何避免回表?
- **创建覆盖索引**:你可以创建一个包含所有查询列的索引,使得查询可以仅通过索引来获取所需数据,从而避免回表。
例如,创建一个覆盖 `name` 和 `student_id` 的联合索引:
```sql
CREATE INDEX idx_name_student_id ON students(name, student_id);
```
这样,当你执行以下查询时:
```sql
SELECT student_id, name FROM students WHERE name = 'Alice';
```
数据库可以直接从索引中获取所有所需列,避免了回表。