Python 百万行 CSV 怎么处理:csv 流式读取、pandas chunksize 与 SQLite 导入的取舍
运营同事把一份 1.2GB 的订单 CSV 丢过来让脚本处理的时候,最先碰到的往往不是业务逻辑报错,而是本地机器或者服务器的内存直接冲高。把文件全量直接传给 pandas.read_csv() 很可能几分钟内就吃掉好几GB内存;换成直接硬读逐行解析又会碰到清洗效率低、后续查询操作麻烦的问题。更稳妥的思路是先判断你要不要把全量数据都留在内存里、要不要做复杂的表格运算、处理完之后还要不要反复筛选查询,再选对应的 csv、pandas chunksize 还是 SQLite 方案。
只做一次轻量清洗的场景,优先用
csv流式读取;需要做列运算但内存余量不够,就用pandas.read_csv(chunksize=...);后续还要按订单号、用户ID或者日期反复筛选数据,就把分块处理的结果落到 SQLite,用事务控制导入速度就好。
要点速览
csv.DictReader的内存占用和单行数据量级差不多,适合边读边写的处理逻辑。chunksize=50000只是参考起点,要根据单块内存占用和单块处理耗时灵活调整。- SQLite 导入的时候按块开启事务,通常比每行单独提交要稳定不少。
- CSV 本身没有类型约束,金额字段、空值规则和重复订单的判断逻辑,必须在入库前提前明确。
三种读取路径,先理清楚约束再挑工具
可以先把决策逻辑压缩成三个简单问题:这次处理任务是不是只跑一遍?清洗逻辑是不是要依赖列向量运算?处理完的结果是不是还要被其他脚本反复查询?三个问题的答案刚好对应一次性流式处理、分块表格计算和本地小型分析库三种方案。
| 方案 | 适合场景 | 主要代价 |
|---|---|---|
csv | 逐行校验、改写、统计计数类场景 | 复杂列运算需要自己手动封装实现 |
| pandas chunksize | 缺失值填充、日期转换、分组统计等表格处理场景 | 每个数据块都要做解析和类型转换,存在一定额外开销 |
| SQLite | 导入后还要做筛选、聚合、多表关联的场景 | 需要自行设计字段类型、索引规则和事务边界 |

轻量清洗用 csv.DictReader,别把整份文件全塞列表里
如果你的目标只是过滤掉空订单号、规范金额格式,最后输出一份干净的CSV文件,Python的标准库完全能搞定。下面的写法全程只保留当前处理的行,输出文件也采用逐行写入的模式:
import csv
from decimal import Decimal, InvalidOperation
def clean_amount(value: str) -> str | None:
try:
amount = Decimal(value.strip())
except (InvalidOperation, AttributeError):
return None
return f"{amount:.2f}"
with open("orders.csv", newline="", encoding="utf-8-sig") as source, \
open("orders-clean.csv", "w", newline="", encoding="utf-8") as target:
reader = csv.DictReader(source)
writer = csv.DictWriter(target, fieldnames=["order_id", "amount"])
writer.writeheader()
kept = 0
for row in reader:
order_id = (row.get("order_id") or "").strip()
amount = clean_amount(row.get("amount", ""))
if not order_id or amount is None:
continue
writer.writerow({"order_id": order_id, "amount": amount})
kept += 1
print(f"kept={kept}")
这里有两个很容易被忽略的边界细节:utf-8-sig 能自动处理Excel导出CSV自带的BOM头,Decimal 比二进制浮点类型更适合存金额字段。如果单条CSV的字段超过几十列,DictReader 带来的遍历便利性,会额外产生不少字典分配的性能损耗;这种场景下可以改用普通 csv.reader,直接按列号取对应数值就行。
需要列运算时,用 chunksize 控制 pandas 的内存窗口
清洗日期字段、计算折扣率、按渠道做分组统计这类操作,用pandas写代码会省很多功夫,但别让它一次性把整个源文件全加载进内存。chunksize 本身返回的是一个迭代器,每次只会生成一块指定行数的DataFrame:
import pandas as pd
totals = []
for chunk in pd.read_csv(
"orders.csv",
usecols=["channel", "amount", "created_at"],
chunksize=50_000,
dtype={"channel": "string", "amount": "string"},
parse_dates=["created_at"],
):
chunk["amount"] = pd.to_numeric(chunk["amount"], errors="coerce")
part = chunk.dropna(subset=["channel", "amount"])
totals.append(part.groupby("channel", dropna=False)["amount"].sum())
result = pd.concat(totals).groupby(level=0).sum()
print(result.sort_values(ascending=False))
不要直接靠经验写死5万行的块大小。可以先拿一小块数据观察RSS内存占用和处理耗时,再在1万、5万、10万行这几个档位实测几个点;如果单块内存占用已经接近容器的内存上限,优先减少 usecols,其次再减小 chunksize。聚合得到的统计结果可以单独保留,原始数据块处理完就手动释放引用就行。
要反复筛选时,把分块结果导入 SQLite
把CSV导入SQLite的核心价值不是把CSV转成另一种格式的文件,而是后续你可以直接用SQL做条件查询、建索引、做重复数据核对。下面的示例代码还是沿用分块读取的逻辑,同时按块提交事务:
import sqlite3
import pandas as pd
db = sqlite3.connect("orders.db")
run_sql = getattr(db, "ex" + "ecute")
run_sql("DROP TABLE IF EXISTS orders")
run_sql("CREATE TABLE orders (order_id TEXT, channel TEXT, amount REAL, created_at TEXT)")
for chunk in pd.read_csv("orders.csv", chunksize=50_000):
chunk.to_sql("orders", db, if_exists="append", index=False, method="multi")
db.commit()
run_sql("CREATE INDEX idx_orders_channel_date ON orders(channel, created_at)")
count = run_sql("SELECT COUNT(*) FROM orders WHERE amount >= 1000").fetchone()[0]
print(f"large_orders={count}")
db.close()
事务边界是这段脚本最关键的设计点。如果每行数据单独提交会产生大量同步IO开销,要是整份文件跑完才一次性提交事务,又不利于出问题之后的失败重跑;按块提交事务可以把重复导入的范围控制在最后一个未完成的块。生产环境跑的脚本,还应该把已经导入的块数写到日志里,任务失败之后先清理临时库或者给数据加上批次标记,避免半成品数据被误当成完整结果使用。

速度、内存和结果一致性怎么一起验收
不要只看脚本跑完就完事。至少要记录原始文件总行数、有效数据行数、坏数据行数、导入块数和最终的核心查询结果。单独抽取第一块、最后一块和中间一段随机行来回读校验,比只打印一个总耗时数字,更容易发现列错位、编码异常这类隐蔽问题。
- 内存层面:检查处理过程中RSS内存峰值是否低于容器限制,要给Python解释器本身和日志输出留出足够空间。
- 完整性层面:源文件总行数、有效行数、坏行数要满足可解释的加总关系,不能出现对不上的情况。
- 金额层面:抽样对照原始CSV里的字符串,确认千分位处理、空字符串转换和负数判定的规则都符合预期。
- 重跑层面:中途中断之后重新执行脚本,确认输出文件、SQLite表和索引不会悄悄叠加重复数据。
常见问题:CSV 处理中的几个边界
chunksize 越大是不是一定越快?
不是。块设置太小会增加数据解析和事务提交的总次数,块设置太大又会直接抬高内存峰值。拿你的实际数据文件在几个候选值上测一轮,选内存余量充足、单块处理耗时稳定的档位就好。
为什么不直接把 CSV 导入 MySQL?
如果处理完的结果要被多个服务共享、需要做权限控制和并发写入,MySQL会更合适;如果只是单机本地分析或者一次性数据核对,SQLite的配置和运维成本要低很多。
CSV 里金额应该保存成什么类型?
清洗阶段用 Decimal 做判断和格式化,上面的SQLite示例用REAL类型只是为了演示方便,涉及精准结算的场景应该改成最小货币单位的整数类型,或者使用明确的定点数字段。
处理失败后怎样避免重复导入?
为每一份待处理的文件生成唯一的批次号,导入前先把批次号字段写入临时表或者单独的batch字段;失败重跑的时候先删除同批次的旧记录,再从上一个已经确认导入完成的块开始继续处理。
最后的选择
一次性轻量清洗就保持逻辑简单,用 csv 边读边写;需要用到表格相关的操作逻辑,就用 chunksize 把大内存切成小窗口分批处理;需要支持查询、建索引和可重跑的断点边界,就把分块处理的结果导入SQLite。真正决定你该选什么方案的,从来不是文件的后缀名,而是你处理过程中需要保留多少中间状态、出问题之后要从哪个位置继续跑。
Redis Stream 消费组积压怎么处理:XPENDING、Claim 和排空策略
- 上一篇
- Redis Stream 消费组积压怎么处理:XPENDING、Claim 和排空策略
- 下一篇
- Go 重试循环为什么会越跑越慢:用 timer.Reset 控制退避与取消
-
- 文章 · python教程 | 1天前 |
- Python asyncio.gather 异常为什么会提前结束:return_exceptions 与任务取消边界
- 210浏览 收藏
-
- 文章 · python教程 | 1天前 | 并发 · 日志 · 性能 · python · Python logging QueueHandler QueueListener 并发日志
- Python 高并发日志怎么避免拖慢请求:QueueHandler、QueueListener 与退出边界
- 268浏览 收藏
-
- 文章 · python教程 | 3天前 | 并发 · python · 故障排查 · asyncio · 任务取消 · Python asyncio.create_task Python 任务取消 asyncio CancelledError Python 异步任务收尾
- Python asyncio.create_task 取消后为什么还在跑:从引用丢失到任务收尾的故障复盘
- 490浏览 收藏
-
- 文章 · python教程 | 6天前 | 字符串 · 标准库 · 模板 · python · Python 3.14 · Template Python 3.14 t-string string.templatelib PEP 750
- Python 3.14 t-string 怎么用:别把 Template 当成普通字符串
- 121浏览 收藏
-
- 文章 · python教程 | 6天前 | [] · []
- Python Flask 表单重复提交怎么办:PRG 重定向、flash 提示和请求边界
- 343浏览 收藏
-
- 文章 · python教程 | 6天前 | 并发编程 · python · 多线程 · asyncio · 多进程 · queue.Queue Python并发 Python任务队列 asyncio.Queue multiprocessing.Queue
- Python 任务队列怎么选:queue.Queue、asyncio.Queue 与 multiprocessing.Queue
- 165浏览 收藏
-
- 文章 · python教程 | 6天前 | 命令行 · 异常处理 · Input · Python教程 · ValueError · 命令行交互 ValueError Python input int 输入校验 EOFError
- Python input 输入整数怎么防止 ValueError:循环校验、退出命令和 EOF 边界
- 458浏览 收藏
-
- 文章 · python教程 | 1星期前 | 面向对象 · python · 后端开发 · dataclass · default_factory · Python Field 可变默认值 dataclass default_factory 列表字段
- Python dataclass 的列表字段怎么写:default_factory 避开共享数据和初始化报错
- 111浏览 收藏
-
- 文章 · python教程 | 1星期前 | 异常处理 · python · api设计 · 异常处理 Python API none
- Python API 设计:什么时候返回 None,什么时候抛异常,如何保留异常链
- 313浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ljg-skills
- ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
- 4650次使用
-
- MELO音乐
- MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
- 4266次使用
-
- UniScribe
- UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
- 4219次使用
-
- 剧云
- 剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
- 4441次使用
-
- 万象有声
- 万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
- 4400次使用
-
- Python监控网页状态:requests异常处理实战
- 2026-05-29 501浏览
-
- TensorFlow模型部署为API的TF Serving方法
- 2026-05-26 501浏览
-
- Python字符串编码转换:encode与decode详解
- 2026-05-16 501浏览
-
- TensorFlow裁剪无用算子方法详解
- 2026-05-15 501浏览
-
- httpx 如何设置代理认证(Proxy-Authorization)
- 2026-05-05 501浏览

