# [SOP]排查 TiDB 执行 Point Get 错误

## Summary

Point Get 通常是基于单个 key 比如 Primary Key(PK) 或 Unique Key(UK) 作为等值条件进行 key->value 的单行数据查询，也称为 Point Select 或 Point Read。

TiDB 对这类以 PK 或 UK 作为条件的等值查询做了特殊的优化，其读效率较高，具有以下特点

* 自动提交的 Point Get select 不用等待获取 PD tso 而使用 max.Uint64 作为 start_ts
* 在 TiDB Optimizer 处理时如果判断为 PointGet 会跳过后续步骤直接走 TryFastPlan 流程，执行计划算子为 Point_Get 或 Batch_Point_Get
* TiKV 中的请求类型为 kv_get 或 kv_batch_get，有专门的线程池 Storage ReadPool 处理，与 Coprocessor ReadPool 隔离（4.0 版本后开启 Unified ReadPool 除外）

在 v5.x 版本，下面几类 SQL（id 为 t 表的 int 类型主键）都支持 Point_Get 或 Batch_Point_Get：

```sql
select \* from t where id=1;
select \* from t where id=1 and col=’a’;
select \* from t where id in (1,2,3);
select \* from t where id=1 or id=2;
```

除了 select，具有类似查询条件的 update 和 delete 在查询部分也支持 Point Get。

## Symptoms

通常 Point Get 查询延迟在毫秒级别，单个 key/value Point Get 的 99 Duration 应在 10ms 以下，但由于一些业务或集群层面（非 Point Get 特有）的原因可能引起 Point Get 延迟抖动，最大延迟甚至达到秒级。

## Cause

Point Get 延迟抖动的常见原因有以下几类：

* 优化器选不上 Point_Get 算子

* TiDB 资源使用过载

* TSO 获取慢

* 读写冲突

* TiDB bach client 问题

* feedback 开启后周期性写入

* Storage ReadPool 线程池打满

* Raft propose & async snapshot 延迟抖动

* KV seek 过多 tombstone key

* 硬件、系统等环境因素

## Diagnostic Guide

**1. 优化器选不上 Point_Get 算子 [ONCALL-940] [ONCALL-1700] [ONCALL-2245] [ONCALL-4094]** 

v4.0 版本开始支持 Point Get 作为 DataSource 之一 [#14044](https://github.com/pingcap/tidb/issues/14044) ，涉及到例如子查询，where 语句包含非 PK 条件列，以及 select 1 等类型的 SQL 也可以选择 Point_Get 算子，在此之前 Point Get 只支持标准的 select \* from t where id=? 语句；v5.0 版本支持多列 DNF 查询条件使用 PointGet，详见 [#18118](https://github.com/pingcap/tidb/issues/18118) 。

> **注意** 
> 
> 目前 Point Get 不支持包含 setvar 表达式定义变量的语句，例如 set @a='1'; select \* from t where id=@a;语句的执行计划无法选上 PointGet。

使用 Prepare Statement 并开启 Plan Cache 会导致 int = non-int 这类条件不能被用于索引访问，包括 Point Get，例如 select \* from t where id=3.1415，id 是 int 类型主键，但参数是浮点数，无法选上索引。

**2. TiDB 资源使用过载导致计算延迟升高 [ONCALL-3991] [ONCALL-1852]** 

TiDB 资源使用过载如 CPU 利用率过高，可能会引起 Go runtime 性能问题，当 Go 调度耗时延迟增加， TiDB 计算处理延迟如 Compile Duration、TSO Wait Duration 也会升高。

TiDB 资源过载有两方面原因，一是业务 QPS 流量上涨超过性能基线，此时可以扩容 TiDB 实例，减少每个实例的负载，二是 TiDB 资源使用方面的 bug 如 goroutine 泄露、内存泄露等，此时可以通过 TiDB Dashboard 对问题实例进行性能分析以获取火焰图、goroutine 等信息帮助进一步诊断。

**3. TSO 获取慢（非自动提交的 Point Get 查询）[ONCALL-3774]** 

除了上面提到的 TiDB 资源使用过载会导致 TSO 获取慢，还有网络等方面的原因，排查步骤参考 [TSO 慢排查手册 Troubleshooting SOP](https://kb.pingcap.com/post/articles/43) 。

**4. 读写冲突 [ONCALL-1728] [ONCALL-2063]** 

当 Point Get 遇到锁也会导致[读写冲突](https://docs.pingcap.com/zh/tidb/stable/troubleshoot-lock-conflicts/#%E8%AF%BB%E5%86%99%E5%86%B2%E7%AA%81) ，特别当其他事务频繁更新热点数据时，写写冲突导致写入变慢，锁不能及时释放，可能出现多次等待写入锁的情况并导致查询时间过长，slow query 可以看到 txnLockFast 类型的多次重试，如 backoff_types:\[txnLockFast,txnLockFast,...]。

v4.0 版本优化了读写冲突问题，TiDB 在事务的 Primary Lock 里保存了 minCommitTs，即事务提交时满足隔离级别的最小的 commit timestamp，当读事务遇到锁时会使用自己的 start timestamp 来更新锁对应事务的 Primary Lock 里的该字段，从而将读写事务进行了强制排序，保证读事务读不到冲突写事务提交的数据。

**5. TiDB bach client 问题 [ONCALL-3213] [ONCALL-3520] [ONCALL-3630] [ONCALL-4074]** 

对比 TiDB 监控 KV Request - KV Request Duration 和 TiKV-Details 监控 gRPC - gRPC message duration，当发现两者统计的延迟有较大差距时，排除网络延迟的原因，很可能是 TiDB 侧的 [tikv-client](https://github.com/tikv/client-go) 存在性能问题，tikv-client 维护了一个连接池（默认最大连接数为 [grpc-connection-count](https://docs.pingcap.com/zh/tidb/stable/tidb-configuration-file/#grpc-connection-count) ），用于和各个 TiKV 进行 gRPC 通信来发送 batch 请求并接收处理响应。当 batch client 连接池处理逻辑存在某些 bug 时，可能导致请求超时或卡住，详见下面的  **Fixed bugs** 。

**6. feedback 周期性写入 [ONCALL-1036] [ONCALL-2114]** 

开启 [feedback](https://docs.pingcap.com/zh/tidb/stable/tidb-configuration-file/#feedback-probability) 功能后，TiDB 默认每隔10分钟会将 feedback 统计信息写入到 TiKV，写入量过大可能导致 TiKV 延迟抖动，通过对比慢查询出现的时间是否落在 TiDB 监控 Statistics- Dump Feedback OPS 的时间周期内判断是否受到 feedback 影响。v4.0.7 开始 feedback 功能默认关闭，暂不建议开启。

**7. Storage ReadPool 线程池打满** 

v4.0 版本之前 Point Get 请求在 TiKV 由 Storage ReadPool 线程池处理，当线程池打满，一些查询请求会排队等待，无法被及时处理导致延迟上涨，此时需要检查各个 TiKV ReadPool CPU 是否存在 CPU 使用不均衡和读热点，如果所有 TiKV 实例都达到 CPU 瓶颈，需要进行扩容，或在机器 CPU 资源充足的前提下调大 [ReadPool 线程数量](https://docs.pingcap.com/zh/tidb/v4.0/tikv-configuration-file/#readpoolstorage)。

v5.0 版本 Unified Pool 功能 GA，升级版本后建议设置 [readpool.storage.use-unified-pool](https://docs.pingcap.com/zh/tidb/v5.0/tikv-configuration-file/#use-unified-pool) 为 true 以使用统一线程池，对 Point Get 类的请求调度到高优先级队列，避免受大查询影响。

**8. Raft propose & async snapshot 延迟抖动 [ONCALL-2328] [ONCALL-3356] [ONCALL-3365]** 

TiKV 中处理查询相关的所有 KV 读都是基于 rocksdb snapshot，snapshot 是 RocksDB - kv 某一个时间点的视图，Point Get 请求通过 Get 接口获取单个 KV。

TiKV 使用 leader lease 机制，在 lease 未过期时一般读请求直接在 read pool 中获取 rocksdb snapshot，当 lease 过期后，需要通过 raftstore 发送 read index 消息更新 leader lease，成功后才能获取 rocksdb snapshot，因此会受到 raftstore 性能的影响。

检查 TiKV-Details 监控 Storage - Storage async snapshot duration 指标，正常应该在毫秒级，当出现延迟抖动可能达到上百毫秒甚至秒级，可能的原因包括网络抖动、leader 调度、lease 过期。对于出现频次较高或周期性的抖动，很可能是 leader lease 过期导致，对比 Raft propose wait duration 与 async snapshot duration 如果抖动时间点能对应上，可以推断是 raftstore 处理变慢导致读请求延迟抖动。

raftstore 的性能优化参考[海量 Region 集群调优最佳实践](https://docs.pingcap.com/zh/tidb/stable/massive-regions-best-practices/)

**9. KV seek 过多 tombstone key [ONCALL-564] [ONCALL-2150] [ONCALL-2379]** 

当业务执行批量删除操作或大事务失败回滚后，kv_get 或 kv_batch_get 查询可能需要 seek 以跳过大量被 GC 掉的 tombstone key，导致性能严重下降，v4.0.12 及后续版本优化了 [#8486](https://github.com/tikv/tikv/issues/8486) 问题。4.0.6 及以上版本可以通过监控 Scheduler - get/batch get 下面的 Scheduler scan details 面板是否存在大量 seek_tombstone 操作定位问题。在业务低峰期使用 [tikv-ctl](https://docs.pingcap.com/zh/tidb/stable/tikv-control/) 手工触发 compaction 清理 tombstone key 可以缓解问题。

**10. 硬件、系统等环境因素** 

TiDB 作为一个分布式数据库，对网络、磁盘等运行环境的稳定性要求较高，生产环境建议按照[官网推荐配置](https://docs.pingcap.com/zh/tidb/stable/hardware-and-software-requirements/#%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83)部署集群，并在上线前进行压测以验证系统的稳定性，也可以用 [tiup cluster check](https://docs.pingcap.com/zh/tidb/stable/tiup-component-cluster-check/#tiup-cluster-check) 检查部署环境是否满足要求。

较高的网络延迟可能引起各组件之间的 rpc 请求延迟升高，对前端业务延迟的影响有明显的放大作用，磁盘高延迟在 block cache miss 的情况下也会导致读延迟不稳定，相关问题可以通过 Grafana 监控 Disk-Performance 的磁盘负载、Node_exporter 的系统状态及 Blackbox_exporter 的 ping latency 延迟辅助诊断。

 **Fixed bugs**  **TiDB**  High GC CPU consumption when running sysbench point select [#25573](https://github.com/pingcap/tidb/issues/25573) statistics: fix a statistics GC problem that can cause duplicated fm-sketch records [#23830](https://github.com/pingcap/tidb/pull/23830) executor: the cached prepared plan is used incorrectly for point get [#24741](https://github.com/pingcap/tidb/issues/24741) executor: fix a panic when batch point get is used for partition table [#23652](https://github.com/pingcap/tidb/pull/23652) 1105 runtime error: invalid memory address or nil pointer dereference [#19041](https://github.com/pingcap/tidb/issues/19041) Unbounded memory usage of BatchGet's cache [#19560](https://github.com/pingcap/tidb/issues/19560)

 **tikv-client**  batchClient may timeout due to use recycled Idle connArray [#22334](https://github.com/pingcap/tidb/issues/22334) internal/client: fix recycle idle connection block sending request [#270](https://github.com/tikv/client-go/pull/270) internal/client: remove the lock for idle connection recycle [#278](https://github.com/tikv/client-go/pull/278)metrics,internal/client: add metrics for batch client stream receiving duration [#279](https://github.com/tikv/client-go/pull/279)

## Solution

目前 PointGet/BatchGet 慢查询耗时的 trace 粒度还有待细化 [#28937](https://github.com/pingcap/tidb/issues/28937)，如 region cache、tso、batch client、rocksdb 等请求链路上的耗时还没有完全记录在慢查询日志中，后续版本将进行优化。

遇到此类问题，建议先按以上几方面原因排查，如需要技术支持，可以使用 tiup diag 命令收集并上传问题时间段的监控和日志等信息到 Clinic Server，并联系 support 人员创建 oncall ticket。

 **相关 KB** 

* [V.4.0.8-sql点查慢](https://kb.pingcap.com/post/solutions/429)