Kubernetes中FastAPI获取Oracle CLOB列内存飙升问题排查
问题描述
在Kubernetes部署的Python FastAPI服务中,使用cx_Oracle查询Oracle数据库的单个CLOB列时,每次请求API都会导致容器内存占用飙升至40%以上,持续请求会引发Pod崩溃,但实际获取的CLOB数据仅为8KB。
使用的FastAPI查询代码:
def fetch_one(self, sql: str, args: Dict[str, Any]) -> Dict[str, Any]: self._cursor.execute(sql, args) self._cursor.rowfactory = self.__make_dict_factory__(self._cursor) rows = self._cursor.fetchone() self.__close__() return rows
针对韩文CLOB设置的输出类型处理器:
def __return_strings_as_bytes__(self, cursor, name, default_type, size, precision, scale): if default_type == cx_Oracle.DB_TYPE_VARCHAR: return cursor.var(str, arraysize=cursor.arraysize, bypass_decode=True) elif default_type == cx_Oracle.DB_TYPE_CHAR: return cursor.var(str, arraysize=cursor.arraysize, bypass_decode=True) elif default_type == cx_Oracle.DB_TYPE_NCHAR: return cursor.var(str, arraysize=cursor.arraysize, bypass_decode=True) elif default_type == cx_Oracle.DB_TYPE_NVARCHAR: return cursor.var(str, arraysize=cursor.arraysize, bypass_decode=True) elif default_type == cx_Oracle.DB_TYPE_CLOB: return cursor.var(str, self.ARRAY_SIZE, cursor.arraysize, bypass_decode=True)
内存占用过高的原因
- CLOB处理器预分配内存过大:CLOB类型处理中指定的
self.ARRAY_SIZE如果远大于实际8KB数据,cx_Oracle会预先分配对应大小的内存块,即便实际数据量很小,也会占用大量内存。 - CLOB资源未显式释放:cx_Oracle的CLOB对象若未被显式读取并关闭,会导致内存无法及时回收,即便调用了
fetchone(),未处理的CLOB对象仍会占用内存。 - 连接/游标资源泄漏:如果
__close__()方法未正确关闭游标和连接,持续请求会导致资源累积,最终耗尽内存。 - bypass_decode的额外内存开销:
bypass_decode=True会让cx_Oracle以原始字节形式返回数据,若后续未正确处理这些字节,内存中会保留不必要的临时数据块。
可行的解决办法
调整CLOB变量大小:将CLOB对应的
cursor.var大小设置为匹配实际数据的数值(比如8192),避免预分配过大内存:elif default_type == cx_Oracle.DB_TYPE_CLOB: return cursor.var(str, 8192, cursor.arraysize, bypass_decode=True)也可以直接省略大小参数,让cx_Oracle根据实际数据自动调整。
显式处理CLOB资源:获取到行数据后,主动读取CLOB内容并关闭对象,释放内存:
def fetch_one(self, sql: str, args: Dict[str, Any]) -> Dict[str, Any]: self._cursor.execute(sql, args) self._cursor.rowfactory = self.__make_dict_factory__(self._cursor) row = self._cursor.fetchone() # 遍历处理所有CLOB列 for key, value in row.items(): if isinstance(value, cx_Oracle.CLOB): row[key] = value.read() value.close() self.__close__() return row修复资源关闭逻辑:确保
__close__()方法彻底关闭游标和连接,避免泄漏:def __close__(self): if hasattr(self, '_cursor') and self._cursor is not None: self._cursor.close() self._cursor = None if hasattr(self, '_connection') and self._connection is not None: self._connection.close() self._connection = None移除不必要的bypass_decode:若不需要原始字节数据,移除
bypass_decode=True,同时确保数据库连接时指定正确编码(如encoding="UTF-8"或nencoding="UTF-8"),让cx_Oracle自动处理韩文编码,减少内存中冗余字节数据。使用连接池复用资源:避免每次请求创建新连接,改用cx_Oracle连接池复用连接,降低资源开销:
# 初始化连接池(全局只执行一次) pool = cx_Oracle.SessionPool(user="your_user", password="your_pwd", dsn="your_dsn", min=2, max=10, increment=1) def fetch_one(self, sql: str, args: Dict[str, Any]) -> Dict[str, Any]: conn = pool.acquire() cursor = conn.cursor() try: cursor.execute(sql, args) cursor.rowfactory = self.__make_dict_factory__(cursor) row = cursor.fetchone() # 处理CLOB列 for key, value in row.items(): if isinstance(value, cx_Oracle.CLOB): row[key] = value.read() value.close() return row finally: cursor.close() pool.release(conn)
内容的提问来源于stack exchange,提问作者ahn
相关产品推荐
相关产品推荐

