#### 8.2.1.2 范围优化

这[`范围`](explain-output.html#jointype_range)访问方法使用单个索引来检索包含在一个或多个索引值区间内的表行的子集。它可用于单部分或多部分索引。以下部分描述了优化器使用范围访问的条件。

-   [单部分索引的范围访问方法](range-optimization.html#range-access-single-part)

-   [多部分索引的范围访问方法](range-optimization.html#range-access-multi-part)

-   [多值比较的等式范围优化](range-optimization.html#equality-range-optimization)

-   [跳过扫描范围访问方法](range-optimization.html#range-access-skip-scan)

-   [行构造函数表达式的范围优化](range-optimization.html#row-constructor-range-optimization)

-   [限制用于范围优化的内存使用](range-optimization.html#range-optimization-memory-use)

##### 单部分索引的范围访问方法

对于单部分索引，索引值区间可以方便地用对应的条件来表示`在哪里`子句，表示为范围条件而不是“间隔”。

单部分索引的范围条件定义如下：

-   对彼此而言`BTREE`和`哈希`索引，关键部分与常数值的比较是使用时的范围条件[`=`](comparison-operators.html#operator_equal),[`<=>`](comparison-operators.html#operator_equal-to),[`在（）`](comparison-operators.html#operator_in),[`一片空白`](comparison-operators.html#operator_is-null)， 要么[`不为空`](comparison-operators.html#operator_is-not-null)运营商。

-   此外，对于`BTREE`索引，关键部分与常数值的比较是使用时的范围条件[`>`](comparison-operators.html#operator_greater-than), [`<`](comparison-operators.html#operator_less-than), [`>=`](comparison-operators.html#operator_greater-than-or-equal), [`<=`](comparison-operators.html#operator_less-than-or-equal), [`之间`](comparison-operators.html#operator_between), [`!=`](comparison-operators.html#operator_not-equal)， 要么[`<>`](comparison-operators.html#operator_not-equal)运营商，或[`喜欢`](string-comparison-functions.html#operator_like)如果参数为[`喜欢`](string-comparison-functions.html#operator_like)是一个不以通配符开头的常量字符串。

-   对于所有索引类型，多个范围条件结合[`要么`](logical-operators.html#operator_or)要么[`和`](logical-operators.html#operator_and)形成一个范围条件。

上述描述中的“常数值”是指以下之一：

-   来自查询字符串的常量

-   一列[`常量`](explain-output.html#jointype_const)要么[`系统`](explain-output.html#jointype_system)来自同一个连接的表

-   不相关子查询的结果

-   任何完全由上述类型的子表达式组成的表达式

    下面是一些带有范围条件的查询示例`在哪里`条款：


```
SELECT * FROM t1
  WHERE key_col > 1
  AND key_col < 10;

SELECT * FROM t1
  WHERE key_col = 1
  OR key_col IN (15,18,20);

SELECT * FROM t1
  WHERE key_col LIKE 'ab%'
  OR key_col BETWEEN 'bar' AND 'foo';
```

在优化器常量传播阶段，一些非常量值可能会转换为常量。

MySQL 尝试从`在哪里`每个可能的索引的子句。在提取过程中，丢弃不能用于构造范围条件的条件，合并产生重叠范围的条件，并去除产生空范围的条件。

考虑以下语句，其中`键1`是一个索引列，并且`非关键`未编入索引：

```
SELECT * FROM t1 WHERE
  (key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR
  (key1 < 'bar' AND nonkey = 4) OR
  (key1 < 'uux' AND key1 > 'z');
```

密钥提取过程`键1`如下：

1.  从原创开始`在哪里`条款：

    ```
    (key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR
    (key1 < 'bar' AND nonkey = 4) OR
    (key1 < 'uux' AND key1 > 'z')
    ```

2.  消除`非关键 = 4`和`key1 LIKE '%b'`因为它们不能用于范围扫描。删除它们的正确方法是将它们替换为`真的`，这样我们在进行范围扫描时就不会错过任何匹配的行。将它们替换为`真的`产量：

    ```
    (key1 < 'abc' AND (key1 LIKE 'abcde%' OR TRUE)) OR
    (key1 < 'bar' AND TRUE) OR
    (key1 < 'uux' AND key1 > 'z')
    ```

3.  始终为真或假的折叠条件：

    -   `（key1 LIKE 'abcde%' OR TRUE）`永远是真的

    -   `(key1 < 'uux' AND key1 > 'z')`总是假的

        用常数替换这些条件会产生：

    ```
    (key1 < 'abc' AND TRUE) OR (key1 < 'bar' AND TRUE) OR (FALSE)
    ```

    去除不必要的`真的`和`错误的`常数产生：

    ```
    (key1 < 'abc') OR (key1 < 'bar')
    ```

4.  将重叠的间隔组合成一个产生用于范围扫描的最终条件：

    ```
    (key1 < 'bar')
    ```

    通常（如前面的示例所示），用于范围扫描的条件比`在哪里`条款。MySQL 执行额外的检查以过滤掉满足范围条件但不是完整的行`在哪里`条款。

    范围条件提取算法可以处理嵌套[`和`](logical-operators.html#operator_and)/[`要么`](logical-operators.html#operator_or)任意深度的构造，其输出不依赖于条件出现的顺序`在哪里`条款。

    MySQL 不支持合并多个范围[`范围`](explain-output.html#jointype_range)空间索引的访问方法。要解决此限制，您可以使用[`联盟`](union.html)与相同的[`选择`](select.html)语句，除了您将每个空间谓词放在不同的[`选择`](select.html).

##### 多部分索引的范围访问方法

多部分索引的范围条件是单部分索引的范围条件的扩展。多部分索引的范围条件将索引行限制在一个或多个键元组间隔内。键元组区间是在一组键元组上定义的，使用索引中的排序。

例如，考虑定义为的多部分索引`键1(*`key_part1`*, *`key_part2`*, *`key_part3`*)`，以及以下按键顺序列出的键元组集：

```
key_part1  key_part2  key_part3
  NULL       1          'abc'
  NULL       1          'xyz'
  NULL       2          'foo'
   1         1          'abc'
   1         1          'xyz'
   1         2          'abc'
   2         1          'aaa'
```

条件`*`key_part1`* = 1`定义这个区间：

```
(1,-inf,-inf) <= (key_part1,key_part2,key_part3) < (1,+inf,+inf)
```

区间覆盖了前面数据集中的第 4、5、6 个元组，可以被范围访问方法使用。

相比之下，条件`*`key_part3`* = 'abc'`没有定义单个区间，不能被范围访问方法使用。

以下描述更详细地说明了范围条件如何对多部分索引起作用。

-   为了`哈希`索引，可以使用包含相同值的每个区间。这意味着只能针对以下形式的条件生成区间：

    ```
        key_part1 cmp const1
    AND key_part2 cmp const2
    AND ...
    AND key_partN cmp constN;
    ```

    这里，*`常量1`*,*`常量2`*, … 是常数，*`cmp`*其中一个[`=`](comparison-operators.html#operator_equal),[`<=>`](comparison-operators.html#operator_equal-to)， 要么[`一片空白`](comparison-operators.html#operator_is-null)比较运算符，条件涵盖所有索引部分。（也就是说，有*`ñ`*条件，每个部分一个*`ñ`*-part 索引。）例如，以下是三部分的范围条件`哈希`指数：

    ```
    key_part1 = 1 AND key_part2 IS NULL AND key_part3 = 'foo'
    ```

    有关被认为是常数的定义，请参见[单部分索引的范围访问方法](range-optimization.html#range-access-single-part).

-   为一个`BTREE`索引，间隔可能可用于与结合的条件[`和`](logical-operators.html#operator_and)，其中每个条件使用一个常数值与一个关键部分进行比较[`=`](comparison-operators.html#operator_equal), [`<=>`](comparison-operators.html#operator_equal-to), [`一片空白`](comparison-operators.html#operator_is-null), [`>`](comparison-operators.html#operator_greater-than), [`<`](comparison-operators.html#operator_less-than), [`>=`](comparison-operators.html#operator_greater-than-or-equal), [`<=`](comparison-operators.html#operator_less-than-or-equal), [`!=`](comparison-operators.html#operator_not-equal), [`<>`](comparison-operators.html#operator_not-equal), [`之间`](comparison-operators.html#operator_between)， 要么[`喜欢 '*`图案`*'`](string-comparison-functions.html#operator_like)（在哪里`'*`图案`*'`不以通配符开头）。只要可以确定包含所有符合条件的行的单个键元组，就可以使用一个区间（或者两个区间，如果[`<>`](comparison-operators.html#operator_not-equal)要么[`!=`](comparison-operators.html#operator_not-equal)用来）。

    优化器尝试使用额外的关键部分来确定区间，只要比较运算符是[`=`](comparison-operators.html#operator_equal),[`<=>`](comparison-operators.html#operator_equal-to)， 要么[`一片空白`](comparison-operators.html#operator_is-null).如果运营商是[`>`](comparison-operators.html#operator_greater-than),[`<`](comparison-operators.html#operator_less-than),[`>=`](comparison-operators.html#operator_greater-than-or-equal),[`<=`](comparison-operators.html#operator_less-than-or-equal),[`!=`](comparison-operators.html#operator_not-equal),[`<>`](comparison-operators.html#operator_not-equal),[`之间`](comparison-operators.html#operator_between)， 要么[`喜欢`](string-comparison-functions.html#operator_like)，优化器使用它但不再考虑关键部分。对于以下表达式，优化器使用[`=`](comparison-operators.html#operator_equal)从第一次比较。它还使用[`>=`](comparison-operators.html#operator_greater-than-or-equal)来自第二个比较，但不考虑进一步的关键部分，并且不使用第三个比较进行区间构造：

    ```
    key_part1 = 'foo' AND key_part2 >= 10 AND key_part3 > 10
    ```

    单个区间为：

    ```
    ('foo',10,-inf) < (key_part1,key_part2,key_part3) < ('foo',+inf,+inf)
    ```

    创建的间隔可能包含比初始条件更多的行。例如，前面的区间包括值`('foo', 11, 0)`，不满足原条件。

-   如果覆盖区间内包含的行集的条件与[`要么`](logical-operators.html#operator_or)，它们形成了一个条件，该条件覆盖了它们的区间并集中包含的一组行。如果条件结合[`和`](logical-operators.html#operator_and)，它们形成了一个条件，该条件涵盖了包含在它们的区间交集内的一组行。例如，对于两部分索引的这种情况：

    ```
    (key_part1 = 1 AND key_part2 < 2) OR (key_part1 > 5)
    ```

    间隔是：

    ```
    (1,-inf) < (key_part1,key_part2) < (1,2)
    (5,-inf) < (key_part1,key_part2)
    ```

    在此示例中，第一行的间隔使用一个关键部分作为左边界，两个关键部分作为右边界。第二行的间隔只使用了一个关键部分。这`key_len`列中的[`解释`](explain.html)输出表示使用的密钥前缀的最大长度。

    在某些情况下，`key_len`可能表明使用了关键部分，但这可能不是您所期望的。假设*`key_part1`*和*`key_part2`*可`空值`.然后`key_len`列显示以下条件的两个关键部分长度：

    ```
    key_part1 >= 1 AND key_part2 < 2
    ```

    但是，实际上，条件转换为：

    ```
    key_part1 >= 1 AND key_part2 IS NOT NULL
    ```

    有关如何执行优化以组合或消除单部分索引上范围条件的间隔的描述，请参阅[单部分索引的范围访问方法](range-optimization.html#range-access-single-part).对多部分索引的范围条件执行类似的步骤。

##### 多值比较的等式范围优化

考虑这些表达式，其中*`col_name`*是一个索引列：

```
col_name IN(val1, ..., valN)
col_name = val1 OR ... OR col_name = valN
```

如果每个表达式都为真*`col_name`*等于多个值中的任何一个。这些比较是相等范围比较（其中“范围”是单个值）。优化器估计读取符合条件的行以进行相等范围比较的成本如下：

-   如果有唯一索引*`col_name`*，每个范围的行估计值为 1，因为最多一行可以具有给定值。

-   否则，任何索引*`col_name`*是非唯一的，优化器可以通过深入索引或索引统计信息来估计每个范围的行数。

[](<>)

使用索引潜水，优化器在范围的每一端进行潜水，并使用范围内的行数作为估计值。例如，表达式`*`col_name`* 在 (10, 20, 30)`具有三个相等范围，优化器对每个范围进行两次潜水以生成行估计。每对潜水都会产生具有给定值的行数的估计值。

索引潜水提供准确的行估计，但随着表达式中比较值数量的增加，优化器需要更长的时间来生成行估计。索引统计的使用不如索引潜水准确，但允许对大值列表进行更快的行估计。

这[`eq_range_index_dive_limit`](server-system-variables.html#sysvar_eq_range_index_dive_limit)系统变量使您能够配置优化器从一种行估计策略切换到另一种的值的数量。允许使用指数潜水来比较高达*`ñ`*相等范围，集合[`均衡范围指数跳水极限`](server-system-variables.html#sysvar_eq_range_index_dive_limit)到*`N`*+ 1. 禁用统计信息并始终使用索引潜水，无论*`N`*设置[`均衡范围指数跳水极限`](server-system-variables.html#sysvar_eq_range_index_dive_limit)到0。

要更新表索引统计信息以获得最佳估计，请使用[`分析表`](analyze-table.html).

在MySQL 8.0之前，除了使用[`均衡范围指数跳水极限`](server-system-variables.html#sysvar_eq_range_index_dive_limit)系统变量。在MySQL 8.0中，对于满足所有这些条件的查询，索引跳转是可能的：

-   查询针对的是单个表，而不是多个表上的联接。

-   单一索引`力指数`存在索引提示。这个想法是，如果强制使用索引，那么在索引中执行潜水操作的额外开销将不会带来任何好处。

-   索引是非均匀的，不是一个`全文`指数

-   不存在子查询。

-   不`不同的`,`分组`或`订购人`条款存在。

    对于[`解释联系`](explain.html)，如果跳过索引潜水，则输出更改如下：

-   对于传统的产出`排`和`过滤`价值观是`无效的`.

-   对于JSON输出，`每次扫描检查的行数`和`每一个连接产生的行数`不要出现，`因受力而跳下`是`符合事实的`，成本计算也不准确。

    没有`连接`, [`解释`](explain.html)跳过索引潜水时，输出不变。

    在执行跳过索引潜水的查询后[`信息模式。优化器跟踪`](information-schema-optimizer-trace-table.html)表包含一个`索引\u潜水\u用于范围\u访问`价值`跳过了_due _to _force _索引`.

##### 跳转扫描范围存取法

考虑下面的情景：

```
CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL, PRIMARY KEY(f1, f2));
INSERT INTO t1 VALUES
  (1,1), (1,2), (1,3), (1,4), (1,5),
  (2,1), (2,2), (2,3), (2,4), (2,5);
INSERT INTO t1 SELECT f1, f2 + 5 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 10 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 20 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 40 FROM t1;
ANALYZE TABLE t1;

EXPLAIN SELECT f1, f2 FROM t1 WHERE f2 > 40;
```

为了执行这个查询，MySQL可以选择一个索引扫描来获取所有行（索引包括所有要选择的列），然后应用`f2>40`来自`哪里`子句生成最终结果集。

范围扫描比全索引扫描更有效，但在这种情况下不能使用，因为不存在启用条件`f1`，第一个索引列。然而，从MySQL 8.0.13开始，优化器可以执行多个范围扫描，每个值一个`f1`，使用类似于松散索引扫描的跳过扫描方法（请参阅[第8.2.1.17节“按优化分组”](group-by-optimization.html)):

1.  在第一个索引部分的不同值之间跳过，`f1`（索引前缀）。

2.  对每个不同的前缀值执行子范围扫描`f2>40`其余索引部分的条件。

    对于前面显示的数据集，算法的操作如下：

3.  获取第一个关键部分的第一个不同值(`f1=1`).

4.  根据第一个和第二个关键部分构建范围(`f1=1，f2>40`).

5.  进行范围扫描。

6.  获取第一个关键部分的下一个不同值(`f1=2`).

7.  根据第一个和第二个关键部分构建范围(`f1=2，f2>40`).

8.  进行范围扫描。

    使用这种策略可以减少访问的行数，因为MySQL会跳过不符合每个构造范围的行。这种跳过扫描访问方法适用于以下情况：

-   表T至少有一个包含表单关键部分的复合索引([A\_1,A...,k\_*`] B_1.B\_*`m`*C[D,1\_D,n...,]).关键部分A和D可以为空，但B和C必须为非空。

-   该查询只引用一个表。

-   该查询不使用`分组`或`不同的`.

-   查询只引用索引中的列。

-   A上的谓词\_1.A.\_*`k`*必须是相等谓词，并且必须是常量。这包括[`在（）`](comparison-operators.html#operator_in)操作人员

-   查询必须是连词查询；就是`和`属于`或`条件：`(*`条件1`*(*`第1部分`*)或者*`条件2`*(*`第1部分`*))及(*`条件1`*(*`第二部分`*)或者……）和`

-   C上一定有射程条件。

-   D柱上的条件是允许的。D上的条件必须与C上的范围条件相结合。

    跳过扫描的使用如中所示`解释`输出如下：

-   `使用索引进行跳过扫描`在`额外的`列表示使用了松散索引跳过扫描访问方法。

-   如果索引可用于跳过扫描，则索引应在`可能的钥匙`柱

    跳过扫描的使用在优化器跟踪输出中由`“跳过扫描”`此表单的元素：


```
"skip_scan_range": {
  "type": "skip_scan",
  "index": index_used_for_skip_scan,
  "key_parts_used_for_access": [key_parts_used_for_access],
  "range": [range]
}
```

你可能还会看到`“最佳跳过扫描摘要”`要素如果选择跳过扫描作为最佳范围访问变量，则`“选择的范围访问摘要”`是这样写的。如果选择跳过扫描作为总体最佳访问方法，则`“最佳访问路径”`元素存在。

跳过扫描的使用取决于[`跳转扫描`](switchable-optimizations.html#optflag_skip-scan)美国国旗[`优化器_开关`](server-system-variables.html#sysvar_optimizer_switch)系统变量。看见[第8.9.2节，“可切换优化”](switchable-optimizations.html).默认情况下，此标志为`在…上`.要禁用它，请设置[`跳转扫描`](switchable-optimizations.html#optflag_skip-scan)到`关`.

除了使用[`优化器_开关`](server-system-variables.html#sysvar_optimizer_switch)系统变量为了控制优化器在整个会话范围内使用跳过扫描，MySQL支持优化器提示，以每语句为基础影响优化器。看见[第8.9.3节，“优化器提示”](optimizer-hints.html).

##### 行构造函数表达式的范围优化

优化器能够将范围扫描访问方法应用于以下形式的查询：

```
SELECT ... FROM t1 WHERE ( col_1, col_2 ) IN (( 'a', 'b' ), ( 'c', 'd' ));
```

以前，要使用范围扫描，必须将查询写为：

```
SELECT ... FROM t1 WHERE ( col_1 = 'a' AND col_2 = 'b' )
OR ( col_1 = 'c' AND col_2 = 'd' );
```

要使优化器使用范围扫描，查询必须满足以下条件：

-   只有[`在（）`](comparison-operators.html#operator_in)使用谓词，而不是[`不在`](comparison-operators.html#operator_not-in).

-   在左边[`在（）`](comparison-operators.html#operator_in)谓词，行构造函数只包含列引用。

-   在右边[`在（）`](comparison-operators.html#operator_in)在谓词中，行构造函数只包含运行时常量，这些常量是在执行期间绑定到常量的文本或本地列引用。

-   在右边[`在（）`](comparison-operators.html#operator_in)谓词，则有多个行构造函数。

    有关优化器和行构造函数的更多信息，请参见[第8.2.1.22节，“行构造函数表达式优化”](row-constructor-optimization.html)

##### 限制内存使用范围优化

要控制范围优化器可用的内存，请使用[`范围\优化器\最大\内存\大小`](server-system-variables.html#sysvar_range_optimizer_max_mem_size)系统变量：

-   值为0表示“无限制”

-   当值大于0时，优化器会在考虑范围访问方法时跟踪所消耗的内存。如果即将超过指定的限制，则放弃范围访问方法，转而考虑其他方法，包括全表扫描。这可能不太理想。如果发生这种情况，将出现以下警告（其中*`N`*是电流吗[`范围\优化器\最大\内存\大小`](server-system-variables.html#sysvar_range_optimizer_max_mem_size)价值）：

    ```
    Warning    3170    Memory capacity of N bytes for
                       'range_optimizer_max_mem_size' exceeded. Range
                       optimization was not done for this query.
    ```

-   对于[`使现代化`](update.html)和[`删去`](delete.html)语句，如果优化器返回到完整表扫描[`sql_安全更新`](server-system-variables.html#sysvar_sql_safe_updates)如果启用了系统变量，则会出现错误而不是警告，因为实际上没有使用键来确定要修改的行。有关更多信息，请参阅[使用安全更新模式（--安全更新）](mysql-tips.html#safe-updates).

    对于超出可用范围优化内存的单个查询，并且对于这些查询，优化器退回到不太优化的计划，增加[`范围\优化器\最大\内存\大小`](server-system-variables.html#sysvar_range_optimizer_max_mem_size)价值可以提高绩效。

    要估计处理范围表达式所需的内存量，请使用以下准则：

-   对于下面这样的简单查询，其中range access方法有一个候选键，每个谓词与[`或`](logical-operators.html#operator_or)使用大约230字节：

    ```
    SELECT COUNT(*) FROM t
    WHERE a=1 OR a=2 OR a=3 OR .. . a=N;
    ```

-   类似地，对于下面这样的查询，每个谓词都与[`和`](logical-operators.html#operator_and)使用大约125字节：

    ```
    SELECT COUNT(*) FROM t
    WHERE a=1 AND b=1 AND c=1 ... N;
    ```

-   询问[`在（）`](comparison-operators.html#operator_in)谓词：

    ```
    SELECT COUNT(*) FROM t
    WHERE a IN (1,2, ..., M) AND b IN (1,2, ..., N);
    ```

    表中的每个文字值[`在（）`](comparison-operators.html#operator_in)列表作为谓词与[`或`](logical-operators.html#operator_or).如果有两个[`在（）`](comparison-operators.html#operator_in)列表中，谓词与[`或`](logical-operators.html#operator_or)是每个列表中文字值数量的乘积。因此，谓词与[`或`](logical-operators.html#operator_or)在前一种情况下*`M`* × *`N`*.
