RDS for MySQL内存过载定位及处理建议
场景介绍
内存过载主要体现以下两个场景:
- 场景一:内存持续缓慢上涨导致OOM。
- 场景二:短时间内存飙升导致OOM。
可能原因
场景一(内存持续缓慢上涨导致OOM)主要包含以下原因:
- 内存参数设置较大,导致全局或每个连接占用的session级别内存较高,且随着执行的SQL变化。
```plaintext show variables where variable_name in ( 'innodb_buffer_pool_size','query_cache_size' ); ```
执行如下命令,查询示例的Session私有内存分配情况:
```plaintext show variables where variable_name in ( 'read_buffer_size','read_rnd_buffer_size','sort_buffer_size','join_buffer_size','binlog_cache_size','tmp_table_size' ); ```
可以适当调整参数使内存稳定在合理范围(75%~90%且稳定无较大波动)。
- 客户端连接空闲超时参数设置过大,导致出现长连接无法释放。
首先检查客户端连接空闲超时断开的参数,比如德鲁伊连接池的**`minEvictableIdleTimeMillis`**和**`maxEvictableIdleTimeMillis`**参数,比如客户端空闲超时时间为60s,但是其连接每秒都会请求sql,不存在空闲时间,则此连接会作为长连接一直存在不释放。
长连接在使用过程中,由于无法保证自身访问的数据量会不会出现变化、自身接收到的SQL文本长度会不会发生变化,有极大可能造成连接的私有内存块net buffer被撑大,且此内存的分配策略为复用不主动回收且随连接释放而释放,最终导致每个长连接占用大块的net buffer内存(最大1G)导致OOM。
- 实例中存在较多的大文本存储过程,导致连接执行存储过程后内存缓慢上升不释放。
存储过程在首次被创建时会进行语法分析和编译,生成执行计划并存储在数据库中,如果连接执行了较大文本的存储过程,被编译好的存储过程会存储在连接的sp_head:mem_root内存块中,且由于其预编译属性,此内存块会在连接释放时才进行释放,如果持续调用不同的存储过程且不释放,数据库内存会呈现上升趋势。
场景二(短时间内存飙升导致OOM)主要包含以下原因:
- SQL文本过大导致net buffer内存占用过大以及词法分析阶段占用内存过大。
RDS for MySQL在处理SQL文本的词法解析以及语法解析时,会将整个SQL的关键字,以及数据信息存储到对应的内存数据结构中,当SQL文本过大时,会占用大量内存导致内存极速上升引发OOM。
常见大SQL主要分为以下几类:
- 类型一: In方式的查询条件中包含大量的查询值导致SQL文本过大。
select column from table where column2 in (a1,a2,a3,a4,a5,a6…aN);
- 类型二:单条insert插入行数过多导致SQL文本过大。
insert into large_data_table values(1,’张三’),(2,’李四’),...(500000,’xxx’)
- 类型三:单条insert涉及文本较长字段,例如:longtext、text、blob、json类型字段。
insert into large_text_table values(1,’xxxx...’),(2,’xxx...’);
- 类型一: In方式的查询条件中包含大量的查询值导致SQL文本过大。
- SQL结果集存在超大单行记录导致瞬时消耗大量内存。
RDS for MySQL在返回结果时,会按照net_buffer_size缓存大小来分批读取结果并发送结果,但缓存的最小单元为单行,如果存在单行结果很大,比如单行记录存在longtext、text、blob、json字段,会导致内存极速上升引发OOM。
SQL举例:
select * from large_text_table limit 1;
被查询表结构中存在大字段longtext,并且存储了1GB数据,单条SQL占用内存大小。
对业务的影响
OOM会导致数据库宕机,通常会导致1分钟以内的连接中断。
处理建议
- 出现过载异常前的建议:
- 排查客户端驱动以及连接池的空闲超时断开参数是否合理。
- 排查业务中的长文本SQL,长文本SQL通常为代码生成,建议设置SQL文本长度的最大值来做拦截。
- 尽量使用业务逻辑代理存储过程。
- 过载异常中的建议:
针对内存缓慢上升的场景,建议杀掉长连接会话,释放部分内存。详见管理实时会话。