从Postgres导出180GB数据到CSV时内存耗尽及磁盘报错排查
代码与环境信息
导出代码
def save_to_csv(self): query = str(self.Text1.get('1.0', 'end-1c')); now = datetime.datetime.now() timestamp_str = now.strftime("%Y-%m-%d_%H-%M-%S") filename = f"result_{timestamp_str}.csv" db = self.db_connect() cur = db.cursor() with open(filename, mode='w', newline='', encoding='utf-8') as csv_file: writer = csv.writer(csv_file) cur.execute('select * from "base_cdek_bf"') № writer.writerow([desc[0] for desc in cur.description]) while True: rows = cur.fetchmany(1000) if not rows: break writer.writerows(rows) csv_file.close() cur.close() db.close()
报错信息
cur.execute('select * from "base_cdek_bf"') psycopg2.errors.InternalError_: could not read block 8810376 in file "base/214899/214911.67": Not enough space
硬件环境
Win10 x64、64GB内存、12核3.6GHz处理器、本地10TB HDD(剩余4TB+),已尝试StringIO()+copy_expert、fetchmany方法均未解决。
问题分析与解决方案
核心问题:报错指向PostgreSQL服务器端磁盘空间不足,而非本地磁盘
报错中的base/214899/214911.67是PostgreSQL服务器端的数据文件路径,这条错误说明服务器端无法读取数据块,因为服务器的磁盘空间不够,和你本地的4TB剩余空间无关。
执行select * from 大表时,PostgreSQL服务器需要在自身磁盘上分配临时空间处理结果集(比如缓存数据、隐含排序操作),180GB的大表会消耗大量服务器端磁盘/内存,这才是报错的根源。
同时代码存在两个语法/冗余问题:
cur.execute行末尾的№是无效语法,必须删除csv_file.close()是冗余代码,with open上下文管理器会自动关闭文件
解决方案
检查并扩容PostgreSQL服务器端磁盘
登录PostgreSQL服务器,检查数据目录(对应报错里的base/路径)所在磁盘的剩余空间,同时检查临时表空间(temp_tablespaces配置项指定的路径)的磁盘空间,清理冗余文件或直接扩容服务器端磁盘。使用PostgreSQL原生
COPY命令替代SELECT+fetchmanyCOPY是PostgreSQL专为批量数据导出设计的命令,比Python逐行读取高效得多,且服务器端资源占用更低。正确的Python调用方式如下:def save_to_csv(self): now = datetime.datetime.now() timestamp_str = now.strftime("%Y-%m-%d_%H-%M-%S") filename = f"result_{timestamp_str}.csv" db = self.db_connect() cur = db.cursor() # 用COPY TO STDOUT将服务器端数据流式输出到本地文件 copy_sql = """COPY "base_cdek_bf" TO STDOUT WITH CSV HEADER ENCODING 'UTF8'""" with open(filename, 'w', newline='', encoding='utf-8') as csv_file: cur.copy_expert(copy_sql, csv_file) cur.close() db.close()该方式不会将所有数据加载到Python内存中,而是直接流式写入本地文件,彻底解决内存耗尽问题。
临时替代方案(无法操作服务器时)
可以尝试用LIMIT+OFFSET分段导出,但这种方式效率较低,且如果导出过程中表有写入操作,可能出现数据重复/遗漏,仅作为临时应急方案。
内容的提问来源于stack exchange,提问作者Yevhen

