https://docs.pingcap.com/zh/tidb/stable/configure-memory-usage
目前 TiDB 已经能够做到追踪单条 SQL 查询过程中的内存使用情况,当内存使用超过一定阈值后也能采取一些操作来预防 OOM 或者排查 OOM 原因。你可以使用系统变量 tidb_mem_oom_action
来控制查询超过内存限制后所采取的操作:
LOG
,那么当一条 SQL 的内存使用超过一定阈值(由 session 变量 tidb_mem_quota_query
控制)后,这条 SQL 会继续执行,但 TiDB 会在 log 文件中打印一条 LOG。CANCEL
,那么当一条 SQL 的内存使用超过一定阈值后,TiDB 会立即中断这条 SQL 的执行,并给客户端返回一个错误,错误信息中会详细写明在这条 SQL 执行过程中占用内存的各个物理执行算子的内存使用情况。使用系统变量 tidb_mem_quota_query
来配置一条 SQL 执行过程中的内存使用阈值,单位为字节。例如:
配置整条 SQL 的内存使用阈值为 8GB:
配置整条 SQL 的内存使用阈值为 8MB:
配置整条 SQL 的内存使用阈值为 8KB:
自 v6.5.0 版本起,可以通过系统变量 tidb_server_memory_limit
设置 tidb-server 实例的内存使用阈值。
例如,配置 tidb-server 实例的内存使用总量,将其设置成为 32 GB:
设置该变量后,当 tidb-server 实例的内存用量达到 32 GB 时,TiDB 会依次终止正在执行的 SQL 操作中内存用量最大的 SQL 操作,直至 tidb-server 实例内存使用下降到 32 GB 以下。被强制终止的 SQL 操作会向客户端返回报错信息 Out Of Memory Quota!
。
当前 tidb_server_memory_limit
所设的内存限制不终止以下 SQL 操作:
在 tidb-server 实例内存用量到达总内存的一定比例时(比例由系统变量 tidb_server_memory_limit_gc_trigger
控制), tidb-server 会尝试主动触发一次 Golang GC 以缓解内存压力。为了避免实例内存在阈值上下范围不断波动导致频繁 GC 进而带来的性能问题,该 GC 方式 1 分钟最多只会触发 1 次。
要查看当前实例或集群的内存使用情况,你可以查询系统表 INFORMATION_SCHEMA.(CLUSTER_)MEMORY_USAGE
。
要查看本实例或集群中内存相关的操作和执行依据,可以查询系统表 INFORMATION_SCHEMA.(CLUSTER_)MEMORY_USAGE_OPS_HISTORY
。对于每个实例,该表保留最近 50 条记录。
当 tidb-server 实例的内存使用量超过内存阈值(默认为总内存量的 70%)且满足以下任一条件时,TiDB 将记录相关状态文件,并打印报警日志。
(本次内存使用量 - 上次报警时内存使用量) / 总内存量 > 10%
。你可以通过系统变量 tidb_memory_usage_alarm_ratio
修改触发该报警的内存使用比率,从而控制内存报警的阈值。
当触发 tidb-server 内存占用过高的报警时,TiDB 的报警行为如下:
TiDB 将以下信息记录到 TiDB 日志文件 filename
所在目录中。
TiDB 将输出一条包含关键字 tidb-server has the risk of OOM
以及以下内存相关系统变量的日志。
为避免报警时产生的状态文件累积过多,目前 TiDB 默认只保留最近 5 次报警时所生成的状态文件。你可以通过配置系统变量 tidb_memory_usage_alarm_keep_record_num
调整该次数。
下例通过构造一个占用大量内存的 SQL 语句触发报警,对该报警功能进行演示:
配置报警比例为 0.85
:
创建单表 CREATE TABLE t(a int);
并插入 1000 行数据。
执行 select * from t t1 join t t2 join t t3 order by t1.a
。该 SQL 语句会输出 1000000000 条记录,占用巨大的内存,进而触发报警。
检查 tidb.log
文件,其中会记录系统总内存、系统当前内存使用量、tidb-server 实例的内存使用量以及状态文件所在目录。
以上 Log 字段的含义如下:
is tidb_server_memory_limit set
:表示系统变量 tidb_server_memory_limit
是否被设置system memory total
:表示当前系统的总内存system memory usage
:表示当前系统的内存使用量tidb-server memory usage
:表示 tidb-server 实例的内存使用量memory-usage-alarm-ratio
:表示系统变量 tidb_memory_usage_alarm_ratio
的值record path
:表示状态文件存放的目录通过访问状态文件所在目录(该示例中的目录为 /tiup/deploy/tidb-4000/log/oom_record
),可以看到标记了记录时间的 record 目录(例:record2022-10-09T17:18:38+08:00
),其中包括 goroutinue
、heap
、running_sql
3 个文件,文件以记录状态文件的时间为后缀。这 3 个文件分别用来记录报警时的 goroutine 栈信息,堆内存使用状态,及正在运行的 SQL 信息。其中 running_sql
文件内容请参考 expensive-queries
。
tidb_distsql_scan_concurrency
所允许的最大线程数来读取数据。当单条 SQL 语句的内存使用每超过 tidb_mem_quota_query
一次,读数据的算子就会停止一个线程。tidb_enable_rate_limit_action
控制。memory exceeds quota, destroy one token now
的日志。TiDB 支持对执行算子的数据落盘功能。当 SQL 的内存使用超过 Memory Quota 时,tidb-server 可以通过落盘执行算子的中间数据,缓解内存压力。支持落盘的算子有:Sort、MergeJoin、HashJoin、HashAgg。
tidb_mem_quota_query
、tidb_enable_tmp_storage_on_oom
、tmp-storage-path
、tmp-storage-quota
共同控制。memory exceeds quota, spill to disk now
或 memory exceeds quota, set aggregate mode to spill-mode
的日志。tidb_executor_concurrency = 1
来触发 HashAgg 落盘功能。本示例通过构造一个占用大量内存的 SQL 语句,对 HashAgg 落盘功能进行演示:
将 SQL 语句的 Memory Quota 配置为 1GB(默认 1GB):
创建单表 CREATE TABLE t(a int);
并插入 256 行不同的数据。
尝试执行以下 SQL 语句:
该 SQL 语句占用大量内存,返回 Out of Memory Quota 错误。
设置系统变量 tidb_executor_concurrency
将执行器的并发度调整为 1。在此配置下,内存不足时 HashAgg 会自动尝试触发落盘。
执行相同的 SQL 语句,不再返回错误,可以执行成功。从详细的执行计划可以看出,HashAgg 使用了 600MB 的硬盘空间。
GOMEMLIMIT
缓解 OOM 问题Golang 自 Go 1.19 版本开始引入 GOMEMLIMIT
环境变量,该变量用来设置触发 Go GC 的内存上限。
对于 v6.1.3 <= TiDB < v6.5.0 的版本,你可以通过手动设置 Go GOMEMLIMIT
环境变量的方式来缓解一类 OOM 问题。该类 OOM 问题具有一个典型特征:观察 Grafana 监控,OOM 前的时刻,TiDB-Runtime > Memory Usage 面板中 estimate-inuse 立柱部分在整个立柱中仅仅占一半。如下图所示:
为了验证 GOMEMLIMIT
在该类场景下的效果,以下通过一个对比实验进行说明:
在 TiDB v6.1.2 下,模拟负载在持续运行几分钟后,TiDB server 会发生 OOM(系统内存约 48 GiB):
在 TiDB v6.1.3 下,设置 GOMEMLIMIT
为 40000 MiB,模拟负载长期稳定运行、TiDB server 未发生 OOM 且进程最高内存用量稳定在 40.8 GiB 左右: