批量插入的预处理语句的执行计划和执行计划缓存

【 TiDB 使用环境】生产环境
【 TiDB 版本】6.5.0

【复现路径】做过哪些操作出现的问题

jdbc 预处理语句,以下选项均开启

useServerPrepStmts
rewriteBatchedStatements
tidb_enable_prepared_plan_cache
tidb_ignore_prepared_cache_close_stmt

批量插入500行一组,因参数过多导致执行计划缓存巨大频繁导致tidb oom,tidb_prepared_plan_cache_size 仅设置为1

【现象】

插入时该语句执行时间如下

Task Type: root

Operator Info: N/A

Root Basic Exec Info:
{
"loops":"1"
"time":"18.8ms"
}
Root Group Exec Info:
[
0:{
"insert":"12.2ms"
"prepare":"6.62ms"
}
1:"lock_keys: {time:10.9ms, region:1, keys:500, slowest_rpc: {total: 0.010s, region_id: 308132995, store: 10.120.33.65:20160, tikv_wall_time: 9.8ms, scan_detail: {get_snapshot_time: 11.1µs, rocksdb: {block: {cache_hit_count: 4016}}}, }, lock_rpc:10.320771ms, rpc_count:1}"
2:NULL
]

【问题】
是否可以消减批量插入的预处理语句的执行计划生成时间(其实不理解插入语句为什么需要每次生成执行计划),另外较高版本批量插入的执行计划是否有机制上的修改?

减少行数呢,看还会不会OOM

差别不是很大,因为我们一次会开较多链接同时插入,按照文档说明的话,应该总是会有这个问题,根本上的话批量插入的prepared stmt每次的bind基本都不一样。但这里sql binding 是指预处理语句的参数还是spm我没有很理解,因为spm文档中没有说明可以为insert语句绑定执行计划


不过比较奇怪的是似乎有一部分的提交看起来是用到计划缓存了,但这又跟oom的现象和文档描述不符

试试 IGNORE_PLAN_CACHE()

ignore_plan_cache

不使用执行计划缓存这一类的功能是好使的,我们之前甚至是全局关闭了执行计划缓存,不过现在其实是希望了解插入语句是否可以把生成执行计划这段消耗优化掉

试试这个,不过你的版本是6.5的,这个参数应该就是解决insert大量数据导致一条语句占用的plancache太大的问题。

内存的问题6.5用tidb_prepared_plan_cache_size就可以控制,但问题是这些语句执行时间里面有很大一部分还是在compile

tidb_prepared_plan_cache_size都设为1了,绝大多数语句都享受不到plancache了,那还不如直接关闭呢吧,这样子设置做做测试还行,实际生产毫无意义。
这个值设为1,大多数SQL语句都会进行compile,肯定会在compile啊。
所以7.1的那个参数就是解决insert大量值的情况,不让其进入plancache,正常的SQL语句可以进入plancache中来解决你这个问题的。
另外所有语句都必须生成执行计划,有了执行计划才有数据存取操作,这个所有数据库都一样的吧(只是有一些数据库没有执行计划缓存)。

可是对于批量插入语句来说的话,这个session只会执行一个预处理语句,如果tidb的执行计划缓存中不存放和校验参数值的话,应该次次都可以用同一个缓存?如果在缓存中存有执行时的参数绑定的话,那对于批量插入语句是不就是必然难以使用执行计划缓存?而且insert语句的执行计划中会包含啥信息呢,它也不像查询类语句那样有不同路径

如果tidb的执行计划缓存中不存放和校验参数值的话,应该次次都可以用同一个缓存? –是的
如果在缓存中存有执行时的参数绑定的话,那对于批量插入语句是不就是必然难以使用执行计划缓存? –很难,因为你每次prepare的stmt都不一样了,因为你prepare时就把stmt搞成不同的了(带了参数值),所以会频繁的在执行计划中淘汰导致可能出现OOM。
而且insert语句的执行计划中会包含啥信息呢,它也不像查询类语句那样有不同路径 –执行时候并不会包含太多信息所以在plancache中保留的执行计划信息是很简单的,最大保存的对象其实就是stmtid产生的语法树结构(里面有大量的values值)占用的较多内存。 这个我记得在某一个版本之后对于insert语句改成了直接忽略plancache了,不过我忘记在哪个版本了,可以查询issue。另外,对于简单的insert into语句可以进入planche效果还是很好的,因为直接免去了硬解析,很大程度上减少cpu的耗时。

emm这么说的话,对于批量插入这种带参数的预处理语句,它的compile时长是免不了的,并且也不能通过执行计划缓存解决?

请问是如何定位到是执行计划缓存引起的 OOM 问题呢

1 个赞

在grafana中看到 tidb executor下 plan cache memory usage在批量插入开始后迅速增高,内存中基本全是它,另外当时还做了dump

这对你这个版本,跑批的相关的session,建议:
1、tidb_prepared_plan_cache_size按照默认值100设置即可。
2、确保该session跑批时候的语句种类不超过tidb_prepared_plan_cache_size,最好不超过10个,这样让这种insert into values很多行的情况可以常驻plancache,一定要注意不要和其它非批次业务混合跑,避免其它非批次语句执行过于频繁导致批次大语句被刷出plancache触发GC问题。
3、控制跑批语句的session是单独的连接(不要和正常的业务放在一个连接池,或者放在一起也不要释放连接,不然可能会污染其它连接,导致所有连接都缓存这个“大语句”的执行计划导致整体内存占用过多)
4、确保insert语句用的是statement=“insert into xx values (?,?,?..)” 绑定变量形式,可以使用plancache。

其实对于你的问题“批量插入500行一组,因参数过多导致执行计划缓存巨大频繁导致tidb oom” ,导致tidb oom的会有两个方面原因:
1、SQL文本确实太大了,多个连接都缓存了这个语句的执行计划,导致整体内存过大。
2、该大语句频繁的被淘汰出plancache,导致后台golang的GC压力过大,可能GC不及时导致占用内存过大最终OOM。

1 个赞

此话题已在最后回复的 60 天后被自动关闭。不再允许新回复。