You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
内存占用过高的原因
  1. CLOB处理器预分配内存过大:CLOB类型处理中指定的self.ARRAY_SIZE如果远大于实际8KB数据,cx_Oracle会预先分配对应大小的内存块,即便实际数据量很小,也会占用大量内存。
  2. CLOB资源未显式释放:cx_Oracle的CLOB对象若未被显式读取并关闭,会导致内存无法及时回收,即便调用了fetchone(),未处理的CLOB对象仍会占用内存。
  3. 连接/游标资源泄漏:如果__close__()方法未正确关闭游标和连接,持续请求会导致资源累积,最终耗尽内存。
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 16:37:10