- 关于 TiDB
- 快速上手
- 应用开发
- 概览
- 快速开始
- 使用 TiDB Cloud (DevTier) 构建 TiDB 集群
- 使用 TiDB 的增删改查 SQL
- TiDB 的简单 CRUD 应用程序
- 示例程序
- 连接到 TiDB
- 数据库模式设计
- 数据写入
- 数据读取
- 事务
- 优化 SQL 性能
- 故障诊断
- 引用文档
- 云原生开发环境
- 部署标准集群
- 数据迁移
- 运维操作
- 监控与告警
- 故障诊断
- 性能调优
- 优化手册
- 配置优化
- SQL 性能调优
- SQL 性能调优概览
- 理解 TiDB 执行计划
- SQL 优化流程
- 控制执行计划
- 教程
- 同城多中心部署
- 两地三中心部署
- 同城两中心部署
- 读取历史数据
- 使用 Stale Read 功能读取历史数据(推荐)
- 使用系统变量
tidb_snapshot
读取历史数据
- 最佳实践
- Placement Rules 使用文档
- Load Base Split 使用文档
- Store Limit 使用文档
- TiDB 工具
- 功能概览
- 适用场景
- 工具下载
- TiUP
- 文档地图
- 概览
- 术语及核心概念
- TiUP 组件管理
- FAQ
- 故障排查
- TiUP 命令参考手册
- 命令概览
- TiUP 命令
- TiUP Cluster 命令
- TiUP Cluster 命令概览
- tiup cluster audit
- tiup cluster check
- tiup cluster clean
- tiup cluster deploy
- tiup cluster destroy
- tiup cluster disable
- tiup cluster display
- tiup cluster edit-config
- tiup cluster enable
- tiup cluster help
- tiup cluster import
- tiup cluster list
- tiup cluster patch
- tiup cluster prune
- tiup cluster reload
- tiup cluster rename
- tiup cluster replay
- tiup cluster restart
- tiup cluster scale-in
- tiup cluster scale-out
- tiup cluster start
- tiup cluster stop
- tiup cluster template
- tiup cluster upgrade
- TiUP DM 命令
- TiUP DM 命令概览
- tiup dm audit
- tiup dm deploy
- tiup dm destroy
- tiup dm disable
- tiup dm display
- tiup dm edit-config
- tiup dm enable
- tiup dm help
- tiup dm import
- tiup dm list
- tiup dm patch
- tiup dm prune
- tiup dm reload
- tiup dm replay
- tiup dm restart
- tiup dm scale-in
- tiup dm scale-out
- tiup dm start
- tiup dm stop
- tiup dm template
- tiup dm upgrade
- TiDB 集群拓扑文件配置
- DM 集群拓扑文件配置
- TiUP 镜像参考指南
- TiUP 组件文档
- PingCAP Clinic 诊断服务 (Technical Preview)
- TiDB Operator
- Dumpling
- TiDB Lightning
- TiDB Data Migration
- 关于 Data Migration
- 快速开始
- 部署 DM 集群
- 入门指南
- 进阶教程
- 运维管理
- 参考手册
- 使用示例
- 异常解决
- 版本发布历史
- Backup & Restore (BR)
- TiDB Binlog
- TiCDC
- sync-diff-inspector
- TiSpark
- 参考指南
- 架构
- 监控指标
- 安全加固
- 权限
- SQL
- SQL 语言结构和语法
- SQL 语句
ADD COLUMN
ADD INDEX
ADMIN
ADMIN CANCEL DDL
ADMIN CHECKSUM TABLE
ADMIN CHECK [TABLE|INDEX]
ADMIN SHOW DDL [JOBS|QUERIES]
ADMIN SHOW TELEMETRY
ALTER DATABASE
ALTER INDEX
ALTER INSTANCE
ALTER PLACEMENT POLICY
ALTER TABLE
ALTER USER
ANALYZE TABLE
BACKUP
BEGIN
CHANGE COLUMN
CHANGE DRAINER
CHANGE PUMP
COMMIT
CREATE [GLOBAL|SESSION] BINDING
CREATE DATABASE
CREATE INDEX
CREATE PLACEMENT POLICY
CREATE ROLE
CREATE SEQUENCE
CREATE TABLE LIKE
CREATE TABLE
CREATE USER
CREATE VIEW
DEALLOCATE
DELETE
DESC
DESCRIBE
DO
DROP [GLOBAL|SESSION] BINDING
DROP COLUMN
DROP DATABASE
DROP INDEX
DROP PLACEMENT POLICY
DROP ROLE
DROP SEQUENCE
DROP STATS
DROP TABLE
DROP USER
DROP VIEW
EXECUTE
EXPLAIN ANALYZE
EXPLAIN
FLASHBACK TABLE
FLUSH PRIVILEGES
FLUSH STATUS
FLUSH TABLES
GRANT <privileges>
GRANT <role>
INSERT
KILL [TIDB]
LOAD DATA
LOAD STATS
MODIFY COLUMN
PREPARE
RECOVER TABLE
RENAME INDEX
RENAME TABLE
REPLACE
RESTORE
REVOKE <privileges>
REVOKE <role>
ROLLBACK
SELECT
SET DEFAULT ROLE
SET [NAMES|CHARACTER SET]
SET PASSWORD
SET ROLE
SET TRANSACTION
SET [GLOBAL|SESSION] <variable>
SHOW [BACKUPS|RESTORES]
SHOW ANALYZE STATUS
SHOW [GLOBAL|SESSION] BINDINGS
SHOW BUILTINS
SHOW CHARACTER SET
SHOW COLLATION
SHOW [FULL] COLUMNS FROM
SHOW CONFIG
SHOW CREATE PLACEMENT POLICY
SHOW CREATE SEQUENCE
SHOW CREATE TABLE
SHOW CREATE USER
SHOW DATABASES
SHOW DRAINER STATUS
SHOW ENGINES
SHOW ERRORS
SHOW [FULL] FIELDS FROM
SHOW GRANTS
SHOW INDEX [FROM|IN]
SHOW INDEXES [FROM|IN]
SHOW KEYS [FROM|IN]
SHOW MASTER STATUS
SHOW PLACEMENT
SHOW PLACEMENT FOR
SHOW PLACEMENT LABELS
SHOW PLUGINS
SHOW PRIVILEGES
SHOW [FULL] PROCESSSLIST
SHOW PROFILES
SHOW PUMP STATUS
SHOW SCHEMAS
SHOW STATS_HEALTHY
SHOW STATS_HISTOGRAMS
SHOW STATS_META
SHOW STATUS
SHOW TABLE NEXT_ROW_ID
SHOW TABLE REGIONS
SHOW TABLE STATUS
SHOW [FULL] TABLES
SHOW [GLOBAL|SESSION] VARIABLES
SHOW WARNINGS
SHUTDOWN
SPLIT REGION
START TRANSACTION
TABLE
TRACE
TRUNCATE
UPDATE
USE
WITH
- 数据类型
- 函数与操作符
- 聚簇索引
- 约束
- 生成列
- SQL 模式
- 表属性
- 事务
- 垃圾回收 (GC)
- 视图
- 分区表
- 临时表
- 缓存表
- 字符集和排序
- Placement Rules in SQL
- 系统表
mysql
- INFORMATION_SCHEMA
- Overview
ANALYZE_STATUS
CLIENT_ERRORS_SUMMARY_BY_HOST
CLIENT_ERRORS_SUMMARY_BY_USER
CLIENT_ERRORS_SUMMARY_GLOBAL
CHARACTER_SETS
CLUSTER_CONFIG
CLUSTER_HARDWARE
CLUSTER_INFO
CLUSTER_LOAD
CLUSTER_LOG
CLUSTER_SYSTEMINFO
COLLATIONS
COLLATION_CHARACTER_SET_APPLICABILITY
COLUMNS
DATA_LOCK_WAITS
DDL_JOBS
DEADLOCKS
ENGINES
INSPECTION_RESULT
INSPECTION_RULES
INSPECTION_SUMMARY
KEY_COLUMN_USAGE
METRICS_SUMMARY
METRICS_TABLES
PARTITIONS
PLACEMENT_POLICIES
PROCESSLIST
REFERENTIAL_CONSTRAINTS
SCHEMATA
SEQUENCES
SESSION_VARIABLES
SLOW_QUERY
STATISTICS
TABLES
TABLE_CONSTRAINTS
TABLE_STORAGE_STATS
TIDB_HOT_REGIONS
TIDB_HOT_REGIONS_HISTORY
TIDB_INDEXES
TIDB_SERVERS_INFO
TIDB_TRX
TIFLASH_REPLICA
TIKV_REGION_PEERS
TIKV_REGION_STATUS
TIKV_STORE_STATUS
USER_PRIVILEGES
VIEWS
METRICS_SCHEMA
- UI
- CLI
- 命令行参数
- 配置文件参数
- 系统变量
- 存储引擎
- 遥测
- 错误码
- 通过拓扑 label 进行副本调度
- 常见问题解答 (FAQ)
- 版本发布历史
- 术语表
执行计划缓存
TiDB 支持对 Prepare
/ Execute
请求的执行计划缓存。其中包括以下两种形式的预处理语句:
- 使用
COM_STMT_PREPARE
和COM_STMT_EXECUTE
的协议功能; - 执行
Prepare
/Execute
SQL 语句查询;
TiDB 优化器对这两类查询的处理是一样的:Prepare
时将参数化的 SQL 查询解析成 AST(抽象语法树),每次 Execute
时根据保存的 AST 和具体的参数值生成执行计划。
当开启执行计划缓存后,每条 Prepare
语句的第一次 Execute
会检查当前查询是否可以使用执行计划缓存,如果可以则将生成的执行计划放进一个由 LRU 链表构成的缓存中;在后续的 Execute
中,会先从缓存中获取执行计划,并检查是否可用,如果获取和检查成功则跳过生成执行计划这一步,否则重新生成执行计划并放入缓存中。
在当前版本中,当 Prepare
语句符合以下条件任何一条,查询或者计划不会被缓存:
SELECT
、UPDATE
、INSERT
、DELETE
、Union
、Intersect
、Except
以外的 SQL 语句;- 访问分区表、临时表或访问表中包含生成列的查询;
- 包含子查询的查询,如
select * from t where a > (select ...)
; - 包含
ignore_plan_cache
这一 Hint 的查询,例如select /*+ ignore_plan_cache() */ * from t
; - 包含除
?
外其他变量(即系统变量或用户自定义变量)的查询,例如select * from t where a>? and b>@x
; - 查询包含无法被缓存函数。目前不能被缓存的函数有:
database()
、current_user
、current_role
、user
、connection_id
、last_insert_id
、row_count
、version
、like
; ?
在Limit
后的查询,如Limit ?
或者Limit 10, ?
,此时?
的具体值对查询性能影响较大,故不缓存;?
直接在Order By
后的查询,如Order By ?
,此时?
表示根据Order By
后第几列排序,排序列不同的查询使用同一个计划可能导致错误结果,故不缓存;如果是普通表达式,如Order By a+?
则会缓存;?
紧跟在Group by
后的查询,如Group By ?
,此时?
表示根据Group By
后第几列聚合,聚合列不同的查询使用同一个计划可能导致错误结果,故不缓存;如果是普通表达式,如Group By a+?
则会缓存;?
出现在窗口函数Window Frame
定义中的查询,如(partition by year order by sale rows ? preceding)
;如果?
出现在窗口函数的其他位置,则会缓存;- 用参数进行
int
和string
比较的查询,如c_int >= ?
或者c_int in (?, ?)
等,其中?
为字符串类型,如set @x='123'
;此时为了保证结果和 MySQL 兼容性,需要每次对参数进行调整,故不会缓存; - 会访问
TiFlash
的计划不会被缓存; - 大部分情况下计划中含有
TableDual
的计划将将不会被缓存,除非当前执行的Prepare
语句不含参数,则对应的TableDual
计划可以被缓存。
LRU 链表是设计成 session 级别的缓存,因为 Prepare
/ Execute
不能跨 session 执行。LRU 链表的每个元素是一个 key-value 对,value 是执行计划,key 由如下几部分组成:
- 执行
Execute
时所在数据库的名字; Prepare
语句的标识符,即紧跟在PREPARE
关键字后的名字;- 当前的 schema 版本,每条执行成功的 DDL 语句会修改 schema 版本;
- 执行
Execute
时的 SQL Mode; - 当前设置的时区,即系统变量
time_zone
的值; - 系统变量
sql_select_limit
的值;
key 中任何一项变动(如切换数据库,重命名 Prepare
语句,执行 DDL,或修改 SQL Mode / time_zone
的值),或 LRU 淘汰机制触发都会导致 Execute
时无法命中执行计划缓存。
成功从缓存中获取到执行计划后,TiDB 会先检查执行计划是否依然合法,如果当前 Execute
在显式事务里执行,并且引用的表在事务前序语句中被修改,而缓存的执行计划对该表访问不包含 UnionScan
算子,则它不能被执行。
在通过合法性检测后,会根据当前最新参数值,对执行计划的扫描范围做相应调整,再用它执行获取数据。
关于执行计划缓存和查询性能有几点值得注意:
- 不管计划是否已经被缓存,都会受到 SQL Binding 的影响。对于没有被缓存的计划,即在第一次执行
Execute
时,会受到已有 SQL Binding 的影响;而对于已经缓存的计划,如果有新的 SQL Binding 被创建产生,则原有已经被缓存的计划会失效。 - 已经被缓存的计划不会受到统计信息更新、优化规则和表达式下推黑名单更新的影响,仍然会使用已经保存在缓存中的计划。
- 重启 TiDB 实例时(如不停机滚动升级 TiDB 集群),
Prepare
信息会丢失,此时执行execute stmt ...
可能会遇到Prepared Statement not found
的错误,此时需要再执行一次prepare stmt ...
。 - 考虑到不同
Execute
的参数会不同,执行计划缓存为了保证适配性会禁止一些和具体参数值密切相关的激进查询优化手段,导致对特定的一些参数值,查询计划可能不是最优。比如查询的过滤条件为where a > ? and a < ?
,第一次Execute
时参数分别为 2 和 1,考虑到这两个参数下次执行时可能会是 1 和 2,优化器不会生成对当前参数最优的TableDual
执行计划。 - 如果不考虑缓存失效和淘汰,一份执行计划缓存会对应各种不同的参数取值,理论上也会导致某些取值下执行计划非最优。比如查询过滤条件为
where a < ?
,假如第一次执行Execute
时用的参数值为 1,此时优化器生成最优的IndexScan
执行计划放入缓存,在后续执行Exeucte
时参数变为 10000,此时TableScan
可能才是更优执行计划,但由于执行计划缓存,执行时还是会使用先前生成的IndexScan
。因此执行计划缓存更适用于查询较为简单(查询编译耗时占比较高)且执行计划较为固定的业务场景。
目前执行计划缓存功能默认关闭,可以通过打开配置文件中 prepared-plan-cache
项启用这项功能。
执行计划缓存功能仅针对 Prepare
/ Execute
请求,对普通查询无效。
在开启了执行计划缓存功能后,可以通过 session 级别的系统变量 last_plan_from_cache
查看上一条 Execute
语句是否使用了缓存的执行计划,例如:
MySQL [test]> create table t(a int);
Query OK, 0 rows affected (0.00 sec)
MySQL [test]> prepare stmt from 'select * from t where a = ?';
Query OK, 0 rows affected (0.00 sec)
MySQL [test]> set @a = 1;
Query OK, 0 rows affected (0.00 sec)
-- 第一次 execute 生成执行计划放入缓存
MySQL [test]> execute stmt using @a;
Empty set (0.00 sec)
MySQL [test]> select @@last_plan_from_cache;
+------------------------+
| @@last_plan_from_cache |
+------------------------+
| 0 |
+------------------------+
1 row in set (0.00 sec)
-- 第二次 execute 命中缓存
MySQL [test]> execute stmt using @a;
Empty set (0.00 sec)
MySQL [test]> select @@last_plan_from_cache;
+------------------------+
| @@last_plan_from_cache |
+------------------------+
| 1 |
+------------------------+
1 row in set (0.00 sec)
如果发现某一组 Prepare
/ Execute
由于执行计划缓存导致了非预期行为,可以通过 SQL Hint ignore_plan_cache()
让该组语句不使用缓存。还是用上述的 stmt
为例:
MySQL [test]> prepare stmt from 'select /*+ ignore_plan_cache() */ * from t where a = ?';
Query OK, 0 rows affected (0.00 sec)
MySQL [test]> set @a = 1;
Query OK, 0 rows affected (0.00 sec)
MySQL [test]> execute stmt using @a;
Empty set (0.00 sec)
MySQL [test]> select @@last_plan_from_cache;
+------------------------+
| @@last_plan_from_cache |
+------------------------+
| 0 |
+------------------------+
1 row in set (0.00 sec)
MySQL [test]> execute stmt using @a;
Empty set (0.00 sec)
MySQL [test]> select @@last_plan_from_cache;
+------------------------+
| @@last_plan_from_cache |
+------------------------+
| 0 |
+------------------------+
1 row in set (0.00 sec)
手动清空计划缓存
通过执行 ADMIN FLUSH [SESSION | INSTANCE] PLAN_CACHE
语句,你可以手动清空计划缓存。
该语句中的作用域 [SESSION | INSTANCE]
用于指定需要清空的缓存级别,可以为 SESSION
或 INSTANCE
。如果不指定作用域,该语句默认清空 SESSION
级别的缓存。
下面是一个清空计划缓存的例子:
MySQL [test]> create table t (a int);
Query OK, 0 rows affected (0.00 sec)
MySQL [test]> prepare stmt from 'select * from t';
Query OK, 0 rows affected (0.00 sec)
MySQL [test]> execute stmt;
Empty set (0.00 sec)
MySQL [test]> execute stmt;
Empty set (0.00 sec)
MySQL [test]> select @@last_plan_from_cache; -- 选择计划缓存
+------------------------+
| @@last_plan_from_cache |
+------------------------+
| 1 |
+------------------------+
1 row in set (0.00 sec)
MySQL [test]> admin flush session plan_cache; -- 清空当前 session 的计划缓存
Query OK, 0 rows affected (0.00 sec)
MySQL [test]> execute stmt;
Empty set (0.00 sec)
MySQL [test]> select @@last_plan_from_cache; -- 由于缓存被清空,此时无法再次选中
+------------------------+
| @@last_plan_from_cache |
+------------------------+
| 0 |
+------------------------+
1 row in set (0.00 sec)
TiDB 暂不支持清空 GLOBAL
级别的计划缓存,即不支持一次性清空整个集群的计划缓存,使用时会报错:
MySQL [test]> admin flush global plan_cache;
ERROR 1105 (HY000): Do not support the 'admin flush global scope.'
忽略 COM_STMT_CLOSE
指令和 DEALLOCATE PREPARE
语句
为了减少每次执行 SQL 语句的语法分析,Prepared Statement 推荐的使用方式是,prepare 一次,然后 execute 多次,最后 deallocate prepare。例如:
MySQL [test]> prepare stmt from '...'; -- prepare 一次
MySQL [test]> execute stmt using ...; -- execute 一次
MySQL [test]> ...
MySQL [test]> execute stmt using ...; -- execute 多次
MySQL [test]> deallocate prepare stmt; -- 使用完成后释放
如果你习惯于在每次 execute 后都立即执行 deallocate prepare,如:
MySQL [test]> prepare stmt from '...'; -- 第一次 prepare
MySQL [test]> execute stmt using ...;
MySQL [test]> deallocate prepare stmt; -- 一次使用后立即释放
MySQL [test]> prepare stmt from '...'; -- 第二次 prepare
MySQL [test]> execute stmt using ...;
MySQL [test]> deallocate prepare stmt; -- 再次释放
这样的使用方式会让第一次执行得到的计划被立即清理,不能在第二次被复用。
为了兼容这样的使用方式,从 v6.0 起,TiDB 支持 tidb_ignore_prepared_cache_close_stmt
变量。打开该变量后,TiDB 会忽略关闭 Prepare Statement 的信号,解决上述问题,如:
mysql> set @@tidb_ignore_prepared_cache_close_stmt=1; -- 打开开关
Query OK, 0 rows affected (0.00 sec)
mysql> prepare stmt from 'select * from t'; -- 第一次 prepare
Query OK, 0 rows affected (0.00 sec)
mysql> execute stmt; -- 第一次 execute
Empty set (0.00 sec)
mysql> deallocate prepare stmt; -- 第一次 execute 后立即释放
Query OK, 0 rows affected (0.00 sec)
mysql> prepare stmt from 'select * from t'; -- 第二次 prepare
Query OK, 0 rows affected (0.00 sec)
mysql> execute stmt; -- 第二次 execute
Empty set (0.00 sec)
mysql> select @@last_plan_from_cache; -- 因为开关打开,第二次依旧能复用上一次的计划
+------------------------+
| @@last_plan_from_cache |
+------------------------+
| 1 |
+------------------------+
1 row in set (0.00 sec)