共计 5816 个字符,预计需要花费 15 分钟才能阅读完成。
与其它支流商业数据库一样,TiDB 的查问优化器负责用户及零碎查问的优化,生成无效且高效的执行打算由执行器来执行。而优化器生成的执行打算的优劣间接影响查问的执行效率和性能。「TiDB 查问优化及调优」系列文章将通过一些具体的案例,向大家介绍 TiDB 查问及优化相干的原理和利用。本文为系列文章的第一篇,将简要介绍 TiDB 的查问优化器的优化流程。
TiDB 中常见的逻辑优化规定
优化器的优化过程能够简略的看成在一个搜寻问题,即针对一条查问,在由各种可能的执行打算形成的微小搜寻空间内寻找到该查问的最优执行打算。不同的数据库查问优化器依据架构不同,对应的优化流程也有所不同。TiDB 的查问优化流程次要分为逻辑优化和物理优化两局部。
在逻辑优化中,利用关系代数的变换规定进行查问语句表达式的等价变换,并在这个过程中一直减少或修剪可能的打算搜寻空间(例如不同的 join order),最初抉择生成最优的逻辑打算树。在之后的物理优化过程中,对逻辑打算树中的算子节点生成理论执行的物理打算,并评估不同物理打算的实现算法(例如不同的 join 办法)或对象(例如应用不同的索引)的代价,从中选取代价最小的物理打算。
上面别离对逻辑优化和物理优化做简介。
逻辑优化是针对逻辑打算中的逻辑算子进行的优化流程。在介绍逻辑优化规定之前,咱们先简介一下 TiDB 中的几种次要逻辑算子:
- DataSource:数据源,示意一个源表,如
select * from t
中的t
。 - Selection:代表了相应的过滤条件,
select * from t where a = 5
中的where a = 5
。 - Projection:投影操作,也用于表达式计算,
select c, a + b from t
外面的c
和a+b
就是投影和表达式计算操作。 - Join:两个表的连贯操作,
select t1.b, t2.c from t1 join t2 on t1.a = t2.a
中的t1 join t2 on t1.a = t2.a
就是两个表t1
和t2
的连贯操作。Join 有内连贯,左连贯,右连贯等多种连贯形式。
Selection,Projection,Join(简称 SPJ)是 3 种最根本的算子。
TiDB 的逻辑优化是基于规定的优化,通过对输出的逻辑执行打算按程序利用优化规定,使整个逻辑执行打算变得更加高效。这些罕用逻辑优化规定包含:
局部逻辑优化规定示例如下:
规定 4:Max / Min 优化
Max/ Min 优化,会对 Max/ Min 语句进行改写。如上面的语句:
select min(id) from t;
改成上面的写法,能够实现相似的成果:
select min(id) from t;
前一个语句生成的执行打算,是一个 TableScan 下面接一个 Aggregation,这是一个全表扫描的操作。后一个语句,生成执行打算是 TableScan + Sort + Limit。通常数据表中的 id 列是主键或者存在索引,数据自身有序,这样 Sort 就能够打消,最终变成 TableScan/IndexLookUp + Limit,这样就防止了全表扫描的操作,只须要读到第一条数据就能返回后果。
最大最小打消由优化器“主动”地做这个变换。
规定 5:外连贯打消
外连贯打消指的是将整个连贯操作从查问中移除。外连贯打消须要满足肯定条件:
- 条件 1:LogicalJoin 的父亲算子只会用到 LogicalJoin 的 outer plan 所输入的列
-
条件 2:
- 条件 2.1:LogicalJoin 中的 join key 在 inner plan 的输入后果中满足唯一性
- 条件 2.2:LogicalJoin 的父亲算子会对输出的记录去重
条件 1 和条件 2 必须同时满足,但条件 2.1 和条件 2.2 只需满足一条即可。
满足条件 1 和 条件 2.1 的一个例子:
select t1.a from t1 left join t2 on t1.b = t2.b;
能够被改写成:
select t1.a from t1;
TiDB 中常见的物理优化
物理优化是基于代价的优化,这一阶段中,优化器会为逻辑执行打算中的每个算子抉择具体的物理实现,以将逻辑优化阶段产生的逻辑执行打算转换成物理执行打算。逻辑算子的不同物理实现有着不同的工夫复杂度、资源耗费和物理属性等。在这个过程中,优化器会依据数据的统计信息来估算不同物理实现的代价,并抉择整体代价最小的物理执行打算。
物理优化须要做的决策有很多,例如:
- 读取数据的形式:应用索引扫描或全表扫描读取数据。
- 如果存在多个索引,索引之间的抉择。
- 逻辑算子的物理实现,即理论应用的算法。
- 是否能够将算子下推到存储层执行,以晋升执行效率。
TiDB 统计信息
统计信息对于查问优化器来说是至关重要的输出信息,优化器将会利用统计信息来估算查问谓词的选择率,查问的各类基数,以及不同算子的代价,并利用这些估算来进行局部逻辑优化以及物理优化。如果统计信息存因为过期或缺失造成较大失真偏差,往往会对优化器的优化造成十分大的影响,从而影响到生成的查问打算。所以在此,咱们会用较大篇幅介绍统计信息,以及相干的收集与保护,因为这是优化器在做查问优化的基石。
TiDB 收集的统计信息包含了表级别和列级别的信息,表的统计信息包含总行数和批改的行数。列的统计信息包含不同值的数量、NULL 的数量、直方图、列上呈现次数最多的值 TOPN 等信息。
TiDB 的统计信息收集包含了手动收集和自动更新两种形式:
- 手动收集:
通过执行 ANALYZE
语句来收集统计信息。以数据库中 person 表为例,应用 analyze 的试行语句如下:
analyze table person;
收集统计信息过程中,能够通过 show analyze status
语句查问执行状态,该语句也能够通过 where
子句对输入后果进行过滤,显示输入后果如下:
mysql> show analyze status where job_info = 'analyze columns';
+--------------+------------+-----------------+---------------------+----------+
| Table_schema | Table_name | Job_info | Start_time | State |
+--------------+------------+-----------------+---------------------+----------+
| test | person | analyze columns | 2020-03-07 06:22:34 | finished |
| test | customer | analyze columns | 2020-03-07 06:32:19 | finished |
| test | person | analyze columns | 2020-03-07 06:35:27 | finished |
+--------------+------------+-----------------+---------------------+----------+
3 rows in set (0.01 sec)
- 自动更新:
在执行 DML 语句时,TiDB 会自动更新表的总行数以及批改的行数。这些信息会定期主动长久化,更新周期默认是 1 分钟(20 * stats-lease)
留神:stats-lease 的默认值是 3s,如果将其设定为 0,则敞开统计信息自动更新。
目前依据统计信息收集和应用的演进,TiDB 目前反对两个版本的统计信息,其中 Version 2 在 Version 1 的根底上做了更多的优化来改善统计信息的保护形式和精度,以及收集效率。具体的差别能够参考 TiDB 统计信息简介文档。
在统计信息收集之后,能够查看统计信息以及表的衰弱度来确认统计信息是否有较大失真。
查看表的统计信息 meta 信息:
mysql> show stats_meta where table_name = 'person';
+---------+------------+----------------+---------------------+--------------+-----------+
| Db_name | Table_name | Partition_name | Update_time | Modify_count | Row_count |
+---------+------------+----------------+---------------------+--------------+-----------+
| test | person | | 2020-03-07 07:20:54 | 0 | 4 |
+---------+------------+----------------+---------------------+--------------+-----------+
1 row in set (0.01 sec)
查看表的衰弱度信息:
mysql> show stats_healthy where table_name = 'person';
+---------+------------+----------------+---------+
| Db_name | Table_name | Partition_name | Healthy |
+---------+------------+----------------+---------+
| test | person | | 100 |
+---------+------------+----------------+---------+
1 row in set (0.00 sec)
可通过 SHOW STATS_HISTOGRAMS
来查看列的不同值数量以及 NULL 值数量等信息:
mysql> show stats_histograms where table_name = 'person';
+---------+------------+----------------+-------------+----------+---------------------+----------------+------------+--------------+-------------+
| Db_name | Table_name | Partition_name | Column_name | Is_index | Update_time | Distinct_count | Null_count | Avg_col_size | Correlation |
+---------+------------+----------------+-------------+----------+---------------------+----------------+------------+--------------+-------------+
| test | person | | name | 0 | 2020-03-07 07:20:54 | 4 | 0 | 6.25 | -0.2 |
+---------+------------+----------------+-------------+----------+---------------------+----------------+------------+--------------+-------------+
1 row in set (0.00 sec)
可通过 SHOW STATS_BUCKETS
来查看直方图每个桶的信息:
mysql> show stats_buckets;
+---------+------------+----------------+-------------+----------+-----------+-------+---------+-------------+-------------+
| Db_name | Table_name | Partition_name | Column_name | Is_index | Bucket_id | Count | Repeats | Lower_Bound | Upper_Bound |
+---------+------------+----------------+-------------+----------+-----------+-------+---------+-------------+-------------+
| test | person | | name | 0 | 0 | 1 | 1 | jack | jack |
| test | person | | name | 0 | 1 | 2 | 1 | peter | peter |
| test | person | | name | 0 | 2 | 3 | 1 | smith | smith |
| test | person | | name | 0 | 3 | 4 | 1 | tom | tom |
+---------+------------+----------------+-------------+----------+-----------+-------+---------+-------------+-------------+
4 rows in set (0.01 sec)
可通过执行 DROP STATS
语句来删除统计信息。语句如下:
mysql> DROP STATS person;
TiDB 的统计信息能够导入导出,不便备份以及值班人员复现定位相干问题。
- 导出:通过以下接口能够获取数据库 ${db_name} 中的表 ${table_name} 的 json 格局的统计信息:
http://${tidb-server-ip}:${tidb-server-status-port}/stats/dump/${db_name}/${table_name}
示例:获取本机上 test 数据库中 person 表的统计信息:
curl -G "http://127.0.0.1:10080/stats/dump/test/person" > person.json
- 导入:将统计信息导出接口失去的 json 文件导入数据库中:
mysql> LOAD STATS 'file_name';
file_name 为被导入的统计信息文件名。
本文为「TiDB 查问优化及调优」系列文章的第一篇,后续将持续对 TiDB 查问打算、慢查问诊断监控及排查、调整及优化查问执行打算以及其余优化器开发或布局中的诊断调优性能等进行介绍。如果您对 TiDB 的产品有任何倡议,欢送来到 https://internals.tidb.io/ 与咱们交换。