# [Critical bug] TiDB 执行计划 index_lookup_hash_join 触发 hange 无法释放 Token，导致 Token-limit 被打满

## Key Words

index_lookup_hash_join, token-limit, hange, hint index join

## Issue

1. Goroutine 卡在 index_lookup_hash_join 上，通过 TiDB profiling 抓取的 Goroutine 文件可以找到如下日志。

   ```markdown
   github.com/pingcap/tidb/executor.(*IndexNestedLoopHashJoin).Close(0xc019e7c840, 0x0, 0x0) 
   --->>> here
   /home/jenkins/agent/workspace/build-common/go/src/github.com/pingcap/tidb/executor/index_lookup_hash_join.go:317 +0xdc 
   --->>> here
   ```

2. SQL 的执行计划为  ( by 'explain for connection session\_id' ) ：`IndexHashJoin` 算子 + `OrderBy` + `LIMIT`。

   ```sql
   -- order by 会在执行计划中 Operator info 结果显示 keep order:true
   Projection_19
   └─Limit_26
     └─IndexHashJoin_172
       ├─Limit_186(Build)
       │ └─IndexHashJoin_149
       │   ├─Limit_157(Build)
       │   │ └─IndexHashJoin_128 
   ```

3. Gorountine 卡住太多 ，甚至会导致 TiDB instance 用尽 `token-limit`，TiDB Sever 无法处理读写请求。

   - 指标为 Grafana 上看到 TiDB -> Server -> Get Token Duration 增高，超过毫秒级别。

## Root Cause

1. 这是已知问题 [GitHub Issue: tidb/issues/35638](https://github.com/pingcap/tidb/issues/35638)。

2. `IndexHashJoin` 的实现问题。`LimitExec.Close()` 会调用 `IndexHashJoinExec.Close()` 。`IndexHashJoinExec` 的 `main routine` 不再从 `channel` 接收 `join` 出来的结果（因为接收数据的逻辑在 `IndexHashJoinExec.Next()` 中），导致 `channel` 被塞满。而 `worker routine` 卡在往 `channel` 塞数据那里，`main routine` 又卡在等待`worker routine`结束那里，导致整个 `hang` 住。

## Diagnostic Steps

1. 通过 debug 中 的 goroutines 找到可疑的 `routine`，观察是否有 hang 住的。
   - 手动抓取 goroutines 的命令 `curl http\://{TiDBIP}:10080/debug/zip?seconds=60 --output debug.zip`
   - Dashboard 平台通过 `Manual Profiling` 手动抓取，官网文档[**Manual Profiling**](https://docs.pingcap.com/zh/tidb/stable/dashboard-profiling)：`v4.0.0` 引入
   - Dashboard 平台通过 `Continuous Profiling` 自动生成，官网文档[**continuous profiling**](https://docs.pingcap.com/zh/tidb/stable/continuous-profiling)：`v5.3.0` 引入

2. 通过 Goroutines 堆栈中 `(*IndexNestedLoopHashJoin).Close` 长时间无法关闭。

3. TiDB 监控中可以看到明显 Token 延迟。

## Resolution

低版本需要通过 Patch 解决，维护中版本需要升级解决。修复内容为：<https://github.com/pingcap/tidb/pull/35820/files> 在 worker routine 塞数据的时候，同时检查   `ctx.Done()` 是否调用，如果已经调用直接返回，避免卡在往 channel 中塞数据那里。

## Workaround

通过 HINT  `/\*+ INL\_JOIN \*/` 指定 'Index Nested Loop Join' 的算法，而不是  'Index Nested Loop Hash Join' 算法，可以绕过，官方文档 [Hint INL_JOIN](https://docs.pingcap.com/zh/tidb/v4.0/optimizer-hints#inl_joint1_name--tl_name-)。

## Affect Version

All of v5.0.x

All of v5.1.x

All of v5.2.x

`<` v5.3.4

`<` v5.4.3

`<` v6.1.1

## Fixed Version

`>=` v5.3.4

`>=` v5.4.3

`>=` v6.1.1
