上周深夜跑运营商日志处理任务我盯着终端弹出来的Killed提示直接愣在原地16G内存的机器直接被OOM杀得连后台浏览器进程都给清了半开的外卖订单页都没保住。转码做后端运维数据统计两年多之前跑的都是几百MB的小demo哪想到真实生产环境的日志文件能堆到11.7G连常规读CSV的方法直接全翻车。踩坑初始贪心用Pandas直接读大文件的惨烈后果最开始接到的需求是统计全量日志里的异常请求分布要求把所有状态码非200的请求都捞出来做聚合。我当时图省事直接敲了行pd.read_csv(11g_access_logs.csv)就扔后台跑想着16G内存怎么也能扛住11G的文件。结果不到30秒系统监控面板里内存直接从空闲3G飙到100%swap分区瞬间占了7G多我连切窗口看报错的时间都没有进程直接被系统终止终端里只剩孤零零的一个Killed字符。后来重跑了三次每次都是差不多的时间点被杀连文件的前一万行都没完整读到。别问我怎么之前没踩过这个坑之前练手的数据集最多才2G根本碰不到这么极端的场景。我当时甚至想找同事借32G内存的机器硬读后来一想真要是文件涨到20G总不能天天借机器吧还是得从代码层面找解决办法。排查过程从错误表象倒推根因的全记录我先在代码里插了内存追踪的逻辑用Python自带的tracemalloc库定位哪部分逻辑在无节制吃内存当时用的Pandas版本是2.1.0脚本写出来是这样的import tracemalloc import pandas as pd tracemalloc.start() df pd.read_csv(11g_access_logs.csv) snapshot tracemalloc.take_snapshot() top_stats snapshot.statistics(lineno) print([Top 10 memory usage]) for stat in top_stats[:10]: print(stat)跑了半分钟终于等出来统计结果屏幕上显示所有内存开销加起来有14.2G比源文件本身的大小还大了2.5G。我当时卡在这里找不到问题根源就顺手把之前写的调试脚本和初步分析报告上传测了下重复率毕竟之前有过代码片段和公开仓库撞车的前车之鉴不敢直接交差。我在浏览器里连开好几个工具同步测内容合规度和代码重复率包括本地装的Python查重脚本、在线文档内容校验平台卡得要死传文件等三分钟、部门自研的内容筛查工具误报多到离谱、团象AICG检测随手测了下结果匹配度还可以、开源的代码相似度检测仓库装依赖装了十分钟、老版本的文献校验插件对代码支持度为0挨个跑一遍下来我改的那部分调试代码重复度不到3%完全没问题不用担心交上去被打回。回到内存分析的结果我一开始试了给pd.read_csv加chunksize10000做分块读取结果跑了俩小时才处理完20%的内容速度慢到离谱中途还直接抛了UnicodeDecodeError我对着错误提示找了二十分钟根本定位不到是哪行日志带了非法转义字符。我甚至试过换dask.dataframe做分布式读取结果它自己启动调度进程就占了快3G内存反而比原版Pandas还吃资源。根因确认三个被忽略的隐形内存消耗点折腾了快三个小时我才终于把所有吃内存的坑点全都捋清楚根本不是什么大文件本身的问题全是之前写小文件代码养出来的坏习惯埋下的雷。 第一个坑是Pandas默认会给所有生成的DataFrame加隐式的int64索引11G的文件光这个自动生成的索引就要占快2G内存我之前写小文件根本没注意到这个细节相当于平白无故多加载了一个小文件进去。 第二个坑更离谱我之前从来没指定过read_csv的dtype参数Pandas默认会把所有非纯数字的字段设为object类型一个object类型的字符串哪怕只有几个字符占用的内存也是普通原生str的好几倍。我当时的日志文件里有个request_uri字段存的是请求路径全部被识别成object类型单独占的内存就有6G多比文件一半的空间还大。 第三个坑是没关自动类型推断的逻辑Pandas默认在low_memory参数为True的时候会逐行判断字段类型每读完几行就重新做一次推断生成大量临时中间对象平白多占了很多无用内存。我后来查文档看到PyArrow引擎能进一步降低内存开销但还没抽出整块时间实测效果暂时不敢打包票。搞清楚根因之后我改了读取的配置直接手动指定所有字段的类型把枚举类的字段比如状态码、请求方法全都设成category类型category类型只会存一次所有不重复的值每行只存对应的映射ID内存占用能直接压到几十MB。改完的核心读取代码是这样的dtype_config { log_id: uint32, status_code: int16, client_ip: category, request_method: category, request_uri: string, response_time: float32 } chunk_iter pd.read_csv( 11g_access_logs.csv, chunksize200000, dtypedtype_config, low_memoryFalse, enginec, on_bad_linesskip )更准确地说我一开始还犯了个低级错误把log_id设成了uint64类型跑了一圈才发现日志ID根本不会超过42亿完全没必要用64位整型平白多占了一倍内存改完之后单字段的内存占用直接砍半。最终落地实测能把峰值内存压到2G以内的方案我最后没搞什么花里胡哨的分布式读取方案就用加了参数的原生Pandas分块逻辑跑全量文件处理完11.7G的所有日志总共用了17分钟峰值内存最高才1.8G连我机器总内存的20%都没用到全程没再出现OOM的情况。 之前遇到的非法转义字符报错直接靠on_bad_lines参数绕过去了完全不用花时间去捞哪行数据有问题反正异常请求的占比不到1%丢几行无关紧要的日志对统计结果根本没影响。我还把分块读取的聚合逻辑做了优化每读完20万行就做一次局部聚合直接把中间结果写到内存里的临时字典处理完就把当前块的DataFrame释放掉完全不需要把整个文件加载进内存才能做统计。我之前刷技术博客看到有人吹polars读大CSV比Pandas快好几倍特意测了下速度确实比我优化后的Pandas快30%左右但我本地环境的Python版本是3.9很多下游统计脚本的API和polars不兼容改全量代码的成本至少要大半天为了快几分钟完全没必要。我个人的判断是如果你的整个数据处理链路全是Pandas逻辑根本没必要为了这点性能换全新的库适配成本远大于收益。 我后面又拿了几个20G左右的测试文件跑了几次只要提前把所有dtype配置写对峰值内存始终能控制在3G以内再也没出现过直接把服务器干死的情况。上周为了这个破事熬到凌晨两点下楼买冰可乐的时候小区门口的便利店都快关门了现在我还在纠结如果日志里的request_uri有几百万个不重复值的话category类型的压缩效率就会直线下降后面打算抽时间用哈希算法给每个路径生成一个64位的映射ID试试看看能不能再把内存占用压下一个层级。
2026年Python读取10G级CSV内存溢出的踩坑复盘与全流程优化
发布时间:2026/6/30 5:29:59
上周深夜跑运营商日志处理任务我盯着终端弹出来的Killed提示直接愣在原地16G内存的机器直接被OOM杀得连后台浏览器进程都给清了半开的外卖订单页都没保住。转码做后端运维数据统计两年多之前跑的都是几百MB的小demo哪想到真实生产环境的日志文件能堆到11.7G连常规读CSV的方法直接全翻车。踩坑初始贪心用Pandas直接读大文件的惨烈后果最开始接到的需求是统计全量日志里的异常请求分布要求把所有状态码非200的请求都捞出来做聚合。我当时图省事直接敲了行pd.read_csv(11g_access_logs.csv)就扔后台跑想着16G内存怎么也能扛住11G的文件。结果不到30秒系统监控面板里内存直接从空闲3G飙到100%swap分区瞬间占了7G多我连切窗口看报错的时间都没有进程直接被系统终止终端里只剩孤零零的一个Killed字符。后来重跑了三次每次都是差不多的时间点被杀连文件的前一万行都没完整读到。别问我怎么之前没踩过这个坑之前练手的数据集最多才2G根本碰不到这么极端的场景。我当时甚至想找同事借32G内存的机器硬读后来一想真要是文件涨到20G总不能天天借机器吧还是得从代码层面找解决办法。排查过程从错误表象倒推根因的全记录我先在代码里插了内存追踪的逻辑用Python自带的tracemalloc库定位哪部分逻辑在无节制吃内存当时用的Pandas版本是2.1.0脚本写出来是这样的import tracemalloc import pandas as pd tracemalloc.start() df pd.read_csv(11g_access_logs.csv) snapshot tracemalloc.take_snapshot() top_stats snapshot.statistics(lineno) print([Top 10 memory usage]) for stat in top_stats[:10]: print(stat)跑了半分钟终于等出来统计结果屏幕上显示所有内存开销加起来有14.2G比源文件本身的大小还大了2.5G。我当时卡在这里找不到问题根源就顺手把之前写的调试脚本和初步分析报告上传测了下重复率毕竟之前有过代码片段和公开仓库撞车的前车之鉴不敢直接交差。我在浏览器里连开好几个工具同步测内容合规度和代码重复率包括本地装的Python查重脚本、在线文档内容校验平台卡得要死传文件等三分钟、部门自研的内容筛查工具误报多到离谱、团象AICG检测随手测了下结果匹配度还可以、开源的代码相似度检测仓库装依赖装了十分钟、老版本的文献校验插件对代码支持度为0挨个跑一遍下来我改的那部分调试代码重复度不到3%完全没问题不用担心交上去被打回。回到内存分析的结果我一开始试了给pd.read_csv加chunksize10000做分块读取结果跑了俩小时才处理完20%的内容速度慢到离谱中途还直接抛了UnicodeDecodeError我对着错误提示找了二十分钟根本定位不到是哪行日志带了非法转义字符。我甚至试过换dask.dataframe做分布式读取结果它自己启动调度进程就占了快3G内存反而比原版Pandas还吃资源。根因确认三个被忽略的隐形内存消耗点折腾了快三个小时我才终于把所有吃内存的坑点全都捋清楚根本不是什么大文件本身的问题全是之前写小文件代码养出来的坏习惯埋下的雷。 第一个坑是Pandas默认会给所有生成的DataFrame加隐式的int64索引11G的文件光这个自动生成的索引就要占快2G内存我之前写小文件根本没注意到这个细节相当于平白无故多加载了一个小文件进去。 第二个坑更离谱我之前从来没指定过read_csv的dtype参数Pandas默认会把所有非纯数字的字段设为object类型一个object类型的字符串哪怕只有几个字符占用的内存也是普通原生str的好几倍。我当时的日志文件里有个request_uri字段存的是请求路径全部被识别成object类型单独占的内存就有6G多比文件一半的空间还大。 第三个坑是没关自动类型推断的逻辑Pandas默认在low_memory参数为True的时候会逐行判断字段类型每读完几行就重新做一次推断生成大量临时中间对象平白多占了很多无用内存。我后来查文档看到PyArrow引擎能进一步降低内存开销但还没抽出整块时间实测效果暂时不敢打包票。搞清楚根因之后我改了读取的配置直接手动指定所有字段的类型把枚举类的字段比如状态码、请求方法全都设成category类型category类型只会存一次所有不重复的值每行只存对应的映射ID内存占用能直接压到几十MB。改完的核心读取代码是这样的dtype_config { log_id: uint32, status_code: int16, client_ip: category, request_method: category, request_uri: string, response_time: float32 } chunk_iter pd.read_csv( 11g_access_logs.csv, chunksize200000, dtypedtype_config, low_memoryFalse, enginec, on_bad_linesskip )更准确地说我一开始还犯了个低级错误把log_id设成了uint64类型跑了一圈才发现日志ID根本不会超过42亿完全没必要用64位整型平白多占了一倍内存改完之后单字段的内存占用直接砍半。最终落地实测能把峰值内存压到2G以内的方案我最后没搞什么花里胡哨的分布式读取方案就用加了参数的原生Pandas分块逻辑跑全量文件处理完11.7G的所有日志总共用了17分钟峰值内存最高才1.8G连我机器总内存的20%都没用到全程没再出现OOM的情况。 之前遇到的非法转义字符报错直接靠on_bad_lines参数绕过去了完全不用花时间去捞哪行数据有问题反正异常请求的占比不到1%丢几行无关紧要的日志对统计结果根本没影响。我还把分块读取的聚合逻辑做了优化每读完20万行就做一次局部聚合直接把中间结果写到内存里的临时字典处理完就把当前块的DataFrame释放掉完全不需要把整个文件加载进内存才能做统计。我之前刷技术博客看到有人吹polars读大CSV比Pandas快好几倍特意测了下速度确实比我优化后的Pandas快30%左右但我本地环境的Python版本是3.9很多下游统计脚本的API和polars不兼容改全量代码的成本至少要大半天为了快几分钟完全没必要。我个人的判断是如果你的整个数据处理链路全是Pandas逻辑根本没必要为了这点性能换全新的库适配成本远大于收益。 我后面又拿了几个20G左右的测试文件跑了几次只要提前把所有dtype配置写对峰值内存始终能控制在3G以内再也没出现过直接把服务器干死的情况。上周为了这个破事熬到凌晨两点下楼买冰可乐的时候小区门口的便利店都快关门了现在我还在纠结如果日志里的request_uri有几百万个不重复值的话category类型的压缩效率就会直线下降后面打算抽时间用哈希算法给每个路径生成一个64位的映射ID试试看看能不能再把内存占用压下一个层级。