Skip to content

Commit

Permalink
planner: support create binding from existing plan (#12187) (#12445)
Browse files Browse the repository at this point in the history
  • Loading branch information
ti-chi-bot committed Dec 28, 2022
1 parent ab7ca72 commit a3a73bd
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 5 deletions.
1 change: 1 addition & 0 deletions experimental-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ summary: 了解 TiDB 各版本的实验特性。
+ [Range INTERVAL 分区](/partitioned-table.md#range-interval-分区)(v6.3.0 实验特性)
+ [使用 TTL (Time to Live) 定期删除过期数据](/time-to-live.md)。(v6.5.0 实验特性)
+ [TiFlash 查询结果物化](/tiflash/tiflash-results-materialization.md)(v6.5.0 实验特性)
+ [根据历史执行计划创建绑定](/sql-plan-management.md#根据历史执行计划创建绑定)(v6.5.0 实验特性)

## 存储

Expand Down
114 changes: 112 additions & 2 deletions sql-plan-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ title: 执行计划管理 (SPM)

### 创建绑定

你可以根据 SQL 或者历史执行计划为指定的 SQL 语句创建绑定。

#### 根据 SQL 创建绑定

{{< copyable "sql" >}}

```sql
Expand Down Expand Up @@ -164,8 +168,102 @@ CREATE BINDING FOR SELECT * FROM t WHERE a > 1 USING SELECT * FROM t use index(i
>
> 对于 `PREPARE`/`EXECUTE` 语句组,或者用二进制协议执行的查询,创建执行计划绑定的对象应当是查询语句本身,而不是 `PREPARE`/`EXECUTE` 语句。
#### 根据历史执行计划创建绑定

如需将 SQL 语句的执行计划固定为之前使用过的执行计划,可以使用 `plan_digest` 为该 SQL 语句绑定一个历史的执行计划。相比于使用 SQL 创建绑定的方式,此方式更加简便。

> **警告:**
>
> 根据历史执行计划创建绑定目前为实验特性,存在未知风险,请勿在生产环境中使用。
目前,根据历史执行计划创建绑定有一些限制:

- 该功能是根据历史的执行计划生成 hint 而实现的绑定,历史的执行计划来源是 [Statement Summary Tables](/statement-summary-tables.md),因此在使用此功能之前需开启系统变量 [`tidb_enable_stmt_summary`](/system-variables.md#tidb_enable_stmt_summary-从-v304-版本开始引入)
- 目前,该功能仅支持根据当前实例中的 `statements_summary``statements_summary_history` 表中的执行计划生成绑定。如果发现有 `can't find any plans` 的情况,请尝试连接集群中其他 TiDB 节点重试。
- 对于带有子查询的查询、访问 TiFlash 的查询、3 张表或更多表进行 Join 的查询,目前还不支持通过历史执行计划进行绑定。

使用方式:

```sql
CREATE [GLOBAL | SESSION] BINDING FROM HISTORY USING PLAN DIGEST 'plan_digest';
```

该语句使用 `plan_digest` 为 SQL 语句绑定执行计划,在不指定作用域时默认作用域为 SESSION。所创建绑定的适用 SQL、优先级、作用域、生效条件等与[根据 SQL 创建绑定](#根据-sql-创建绑定)相同。

使用此绑定方式时,你需要先从 `statements_summary` 中找到需要绑定的执行计划对应的 `plan_digest`,再通过 `plan_digest` 创建绑定。具体步骤如下:

1.`Statement Summary Tables` 的记录中查找执行计划对应的 `plan_digest`

例如:

```sql
CREATE TABLE t(id INT PRIMARY KEY , a INT, KEY(a));
SELECT /*+ IGNORE_INDEX(t, a) */ * FROM t WHERE a = 1;
SELECT * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY WHERE QUERY_SAMPLE_TEXT = 'SELECT /*+ IGNORE_INDEX(t, a) */ * FROM t WHERE a = 1'\G;
```

以下为 `statements_summary` 部分查询结果:

```
SUMMARY_BEGIN_TIME: 2022-12-01 19:00:00
...........
DIGEST_TEXT: select * from `t` where `a` = ?
...........
PLAN_DIGEST: 4e3159169cc63c14b139a4e7d72eae1759875c9a9581f94bb2079aae961189cb
PLAN: id task estRows operator info actRows execution info memory disk
TableReader_7 root 10 data:Selection_6 0 time:4.05ms, loops:1, cop_task: {num: 1, max: 598.6µs, proc_keys: 0, rpc_num: 2, rpc_time: 609.8µs, copr_cache_hit_ratio: 0.00, distsql_concurrency: 15} 176 Bytes N/A
└─Selection_6 cop[tikv] 10 eq(test.t.a, 1) 0 tikv_task:{time:560.8µs, loops:0} N/A N/A
└─TableFullScan_5 cop[tikv] 10000 table:t, keep order:false, stats:pseudo 0 tikv_task:{time:560.8µs, loops:0} N/A N/A
BINARY_PLAN: 6QOYCuQDCg1UYWJsZVJlYWRlcl83Ev8BCgtTZWxlY3Rpb25fNhKOAQoPBSJQRnVsbFNjYW5fNSEBAAAAOA0/QSkAAQHwW4jDQDgCQAJKCwoJCgR0ZXN0EgF0Uh5rZWVwIG9yZGVyOmZhbHNlLCBzdGF0czpwc2V1ZG9qInRpa3ZfdGFzazp7dGltZTo1NjAuOMK1cywgbG9vcHM6MH1w////CQMEAXgJCBD///8BIQFzCDhVQw19BAAkBX0QUg9lcSgBfCAudC5hLCAxKWrmYQAYHOi0gc6hBB1hJAFAAVIQZGF0YTo9GgRaFAW4HDQuMDVtcywgCbYcMWKEAWNvcF8F2agge251bTogMSwgbWF4OiA1OTguNsK1cywgcHJvY19rZXlzOiAwLCBycGNfBSkAMgkMBVcQIDYwOS4pEPBDY29wcl9jYWNoZV9oaXRfcmF0aW86IDAuMDAsIGRpc3RzcWxfY29uY3VycmVuY3k6IDE1fXCwAXj///////////8BGAE=
```

可以看到执行计划对应的 `plan_digest``4e3159169cc63c14b139a4e7d72eae1759875c9a9581f94bb2079aae961189cb`

2. 使用 `plan_digest` 创建绑定。

```sql
CREATE BINDING FROM HISTORY USING PLAN DIGEST '4e3159169cc63c14b139a4e7d72eae1759875c9a9581f94bb2079aae961189cb';
```

创建完毕后可以[查看绑定](#查看绑定),验证绑定是否生效。

```sql
SHOW BINDINGS\G;
```

```
*************************** 1. row ***************************
Original_sql: select * from `test` . `t` where `a` = ?
Bind_sql: SELECT /*+ use_index(@`sel_1` `test`.`t` ) ignore_index(`t` `a`)*/ * FROM `test`.`t` WHERE `a` = 1
...........
Sql_digest: 6909a1bbce5f64ade0a532d7058dd77b6ad5d5068aee22a531304280de48349f
Plan_digest:
1 row in set (0.01 sec)

ERROR:
No query specified
```
```sql
SELECT * FROM t WHERE a = 1;
SELECT @@LAST_PLAN_FROM_BINDING;
```

```
+--------------------------+
| @@LAST_PLAN_FROM_BINDING |
+--------------------------+
| 1 |
+--------------------------+
1 row in set (0.00 sec)
```

### 删除绑定

你可以根据 SQL 语句或者 `sql_digest` 删除绑定。

#### 根据 SQL 语句删除绑定

{{< copyable "sql" >}}

```sql
Expand All @@ -188,6 +286,16 @@ explain SELECT * FROM t1,t2 WHERE t1.id = t2.id;

在这里 SESSION 作用域内被删除掉的绑定会屏蔽 GLOBAL 作用域内相应的绑定,优化器不会为 `SELECT` 语句添加 `sm_join(t1, t2)` hint,`explain` 给出的执行计划中最上层节点并不被 hint 固定为 MergeJoin,而是由优化器经过代价估算后自主进行选择。

#### 根据 `sql_digest` 删除绑定

除了可以根据 SQL 语句删除对应的绑定以外,也可以根据 `sql_digest` 删除绑定:

```sql
DROP [GLOBAL | SESSION] BINDING FOR SQL DIGEST 'sql_digest';
```

该语句用于在 GLOBAL 或者 SESSION 作用域内删除 `sql_digest` 对应的的执行计划绑定,在不指定作用域时默认作用域为 SESSION。你可以通过[查看绑定](#查看绑定)语句获取 `sql_digest`

> **注意:**
>
> 执行 `DROP GLOBAL BINDING` 会删除当前 tidb-server 实例缓存中的绑定,并将系统表中对应行的状态修改为 'deleted'。该语句不会直接删除系统表中的记录,因为其他 tidb-server 实例需要读取系统表中的 'deleted' 状态来删除其缓存中对应的绑定。对于这些系统表中状态为 'deleted' 的记录,后台线程每隔 100 个 `bind-info-lease`(默认值为 `3s`,合计 `300s`)会触发一次对 `update_time` 在 10 个 `bind-info-lease` 以前的绑定(确保所有 tidb-server 实例已经读取过这个 'deleted' 状态并更新完缓存)的回收清除操作。
Expand All @@ -212,7 +320,7 @@ SET BINDING [ENABLED | DISABLED] FOR BindableStmt;
SHOW [GLOBAL | SESSION] BINDINGS [ShowLikeOrWhere];
```

该语句会按照绑定更新时间由新到旧的顺序输出 GLOBAL 或者 SESSION 作用域内的执行计划绑定,在不指定作用域时默认作用域为 SESSION。目前 `SHOW BINDINGS` 会输出 8 列,具体如下:
该语句会按照绑定更新时间由新到旧的顺序输出 GLOBAL 或者 SESSION 作用域内的执行计划绑定,在不指定作用域时默认作用域为 SESSION。目前 `SHOW BINDINGS` 会输出 11 列,具体如下:

| 列名 | 说明 |
| -------- | ------------- |
Expand All @@ -224,7 +332,9 @@ SHOW [GLOBAL | SESSION] BINDINGS [ShowLikeOrWhere];
| update_time | 更新时间 |
| charset | 字符集 |
| collation | 排序规则 |
| source | 创建方式,包括 manual (由 `create [global] binding` 生成)、capture(由 tidb 自动创建生成)和 evolve (由 tidb 自动演进生成) |
| source | 创建方式,包括 manual(根据 SQL 创建绑定生成)、history(根据历史执行计划创建绑定生成)、capture(由 TiDB 自动创建生成)和 evolve (由 TiDB 自动演进生成) |
| sql_digest | 归一化后的 SQL 的 digest |
| plan_digest | 执行计划的 digest |

### 排查绑定

Expand Down
42 changes: 40 additions & 2 deletions sql-statements/sql-statement-create-binding.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ summary: TiDB 数据库中 CREATE [GLOBAL|SESSION] BINDING 的使用概况。

`BINDING` 语句可以在 `GLOBAL` 或者 `SESSION` 作用域内创建执行计划绑定。在不指定作用域时,默认的作用域为 `SESSION`

被绑定的 SQL 语句会被参数化后存储到系统表中。在处理 SQL 查询时,只要参数化后的 SQL 语句和系统表中某个被绑定的 SQL 语句一致,并且系统变量 `tidb_use_plan_baselines` 的值为 `ON`(其默认值为 `ON`),即可使用相应的优化器 Hint。如果存在多个可匹配的执行计划,优化器会从中选择代价最小的一个进行绑定。
被绑定的 SQL 语句会被参数化后存储到系统表中。在处理 SQL 查询时,只要参数化后的 SQL 语句和系统表中某个被绑定的 SQL 语句一致,并且系统变量 `tidb_use_plan_baselines` 的值为 `ON`(其默认值为 `ON`),即可使用相应的优化器 Hint。如果存在多个可匹配的执行计划,优化器会从中选择代价最小的一个进行绑定。更多信息,请参考[创建绑定](/sql-plan-management.md#创建绑定)

## 语法图

```ebnf+diagram
CreateBindingStmt ::=
'CREATE' GlobalScope 'BINDING' 'FOR' BindableStmt 'USING' BindableStmt
'CREATE' GlobalScope 'BINDING' ( 'FOR' BindableStmt 'USING' BindableStmt )
| ( 'FROM' 'HISTORY' 'USING' 'PLAN' 'DIGEST' PlanDigest )
GlobalScope ::=
( 'GLOBAL' | 'SESSION' )?
Expand All @@ -28,6 +29,10 @@ BindableStmt ::=

## 示例

你可以根据 SQL 或历史执行计划创建绑定。

下面的示例演示如何根据 SQL 创建绑定。

{{< copyable "sql" >}}

```sql
Expand Down Expand Up @@ -130,6 +135,39 @@ EXPLAIN ANALYZE SELECT * FROM t1 WHERE b = 123;
3 rows in set (0.01 sec)
```

下面的示例演示如何根据历史执行计划创建绑定。

```sql
mysql> CREATE TABLE t(id INT PRIMARY KEY , a INT, KEY(a));
Query OK, 0 rows affected (0.06 sec)

mysql> SELECT /*+ IGNORE_INDEX(t, a) */ * FROM t WHERE a = 1;
Empty set (0.01 sec)

mysql> SELECT plan_digest FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY WHERE QUERY_SAMPLE_TEXT = 'SELECT /*+ IGNORE_INDEX(t, a) */ * FROM t WHERE a = 1';
+------------------------------------------------------------------+
| plan_digest |
+------------------------------------------------------------------+
| 4e3159169cc63c14b139a4e7d72eae1759875c9a9581f94bb2079aae961189cb |
+------------------------------------------------------------------+
1 row in set (0.01 sec)

mysql> CREATE BINDING FROM HISTORY USING PLAN DIGEST '4e3159169cc63c14b139a4e7d72eae1759875c9a9581f94bb2079aae961189cb';
Query OK, 0 rows affected (0.02 sec)

mysql> SELECT * FROM t WHERE a = 1;
Empty set (0.01 sec)

mysql> SELECT @@LAST_PLAN_FROM_BINDING;
+--------------------------+
| @@LAST_PLAN_FROM_BINDING |
+--------------------------+
| 1 |
+--------------------------+
1 row in set (0.01 sec)

```

## MySQL 兼容性

`CREATE [GLOBAL|SESSION] BINDING` 语句是 TiDB 对 MySQL 语法的扩展。
Expand Down
66 changes: 65 additions & 1 deletion sql-statements/sql-statement-drop-binding.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ summary: TiDB 数据库中 DROP [GLOBAL|SESSION] BINDING 的使用概况。

```ebnf+diagram
DropBindingStmt ::=
'DROP' GlobalScope 'BINDING' 'FOR' BindableStmt ( 'USING' BindableStmt )?
'DROP' GlobalScope 'BINDING' 'FOR' ( BindableStmt ( 'USING' BindableStmt )? )
| ('SQL' 'DIGEST' SqlDigest)
GlobalScope ::=
( 'GLOBAL' | 'SESSION' )?
Expand All @@ -24,6 +25,10 @@ BindableStmt ::=

## 示例

你可以根据 SQL 语句或 `sql_digest` 删除绑定。

下面的示例演示如何根据 SQL 语句删除绑定。

{{< copyable "sql" >}}

```sql
Expand Down Expand Up @@ -129,6 +134,65 @@ SHOW SESSION BINDINGS\G
Empty set (0.00 sec)
```

下面的示例演示如何根据 `sql_digest` 删除绑定。

```sql
mysql> CREATE TABLE t(id INT PRIMARY KEY , a INT, KEY(a));
Query OK, 0 rows affected (0.06 sec)

mysql> SELECT /*+ IGNORE_INDEX(t, a) */ * FROM t WHERE a = 1;
Empty set (0.01 sec)

mysql> SELECT plan_digest FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY WHERE QUERY_SAMPLE_TEXT = 'SELECT /*+ IGNORE_INDEX(t, a) */ * FROM t WHERE a = 1';
+------------------------------------------------------------------+
| plan_digest |
+------------------------------------------------------------------+
| 4e3159169cc63c14b139a4e7d72eae1759875c9a9581f94bb2079aae961189cb |
+------------------------------------------------------------------+
1 row in set (0.01 sec)

mysql> CREATE BINDING FROM HISTORY USING PLAN DIGEST '4e3159169cc63c14b139a4e7d72eae1759875c9a9581f94bb2079aae961189cb';
Query OK, 0 rows affected (0.02 sec)

mysql> SELECT * FROM t WHERE a = 1;
Empty set (0.01 sec)

mysql> SELECT @@LAST_PLAN_FROM_BINDING;
+--------------------------+
| @@LAST_PLAN_FROM_BINDING |
+--------------------------+
| 1 |
+--------------------------+
1 row in set (0.01 sec)

mysql> SHOW BINDINGS\G;
*************************** 1. row ***************************
Original_sql: select * from `test` . `t` where `a` = ?
Bind_sql: SELECT /*+ use_index(@`sel_1` `test`.`t` ) ignore_index(`t` `a`)*/ * FROM `test`.`t` WHERE `a` = 1
Default_db: test
Status: enabled
Create_time: 2022-12-14 15:26:22.277
Update_time: 2022-12-14 15:26:22.277
Charset: utf8mb4
Collation: utf8mb4_general_ci
Source: history
Sql_digest: 6909a1bbce5f64ade0a532d7058dd77b6ad5d5068aee22a531304280de48349f
Plan_digest: 4e3159169cc63c14b139a4e7d72eae1759875c9a9581f94bb2079aae961189cb
1 row in set (0.02 sec)

ERROR:
No query specified

mysql> DROP BINDING FOR SQL DIGEST '6909a1bbce5f64ade0a532d7058dd77b6ad5d5068aee22a531304280de48349f';
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW BINDINGS\G;
Empty set (0.01 sec)

ERROR:
No query specified
```

## MySQL 兼容性

`DROP [GLOBAL|SESSION] BINDING` 语句是 TiDB 对 MySQL 语法的扩展。
Expand Down

0 comments on commit a3a73bd

Please sign in to comment.