Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

planner: support create binding from existing plan (#12187) #12445

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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