应使用 pd.read_sql_query 的 limit+主键范围分片(如 id between ? and ?)替代 offset,配合显式 dtype、索引优化及 wal 模式,避免内存爆炸与全表扫描。

用 pd.read_sql_query() 分块读取避免内存爆炸
直接 pd.read_sql_query("SELECT * FROM big_table", conn) 很可能让 Python 进程 OOM,尤其表超千万行。SQLite 本身不支持服务端游标分页,但可以靠 LIMIT + OFFSET 或更稳妥的主键/时间戳范围切分。
推荐用主键(如 id)做增量分片,比 OFFSET 更高效(避免全表扫描):
min_id, max_id = pd.read_sql_query("SELECT MIN(id), MAX(id) FROM big_table", conn).iloc[0]
chunk_size = 50000
for start in range(min_id, max_id + 1, chunk_size):
df_chunk = pd.read_sql_query(
"SELECT * FROM big_table WHERE id BETWEEN ? AND ?",
conn,
params=(start, min(start + chunk_size - 1, max_id))
)
# 处理 df_chunk,例如写入 Parquet、聚合、清洗
-
OFFSET在大偏移量下性能急剧下降,务必避开 - 确保
id有索引,否则每次查询仍是全表扫描 - 如果主键不连续或缺失,改用时间字段(如
created_at),配合BETWEEN和索引
写入时禁用事务自动提交,用 executemany 批量插入
用 df.to_sql() 默认每行启一个事务,写百万行可能耗时数分钟。关键不是换函数,而是控制事务粒度和底层执行方式。
手动批量插入更快:
conn.execute("BEGIN")
conn.executemany(
"INSERT INTO target_table (col1, col2) VALUES (?, ?)",
[tuple(row) for row in df.itertuples(index=False, name=None)]
)
conn.execute("COMMIT")
-
df.to_sql(..., if_exists='append', index=False)可用,但必须加method='multi'参数启用批量模式 - SQLite 默认
journal_mode = DELETE,写入前建议设为WAL:conn.execute("PRAGMA journal_mode = WAL") - 关闭自动提交后,记得显式
COMMIT,否则数据不落盘
用 dtype 显式声明列类型减少内存占用
SQLite 是动态类型,Pandas 读取时默认全推断为 object 或 float64,一张千万行文本表可能吃掉 8GB 内存。必须主动压缩。
在 read_sql_query 中传 dtype:
dtypes = {
"user_id": "category",
"status": "category",
"amount": "float32",
"created_at": "datetime64[ns]"
}
df = pd.read_sql_query(sql, conn, dtype=dtypes)
-
category对低基数字符串(如状态码、地区名)压缩率极高 -
float32足够覆盖多数业务精度,省一半内存 -
datetime64[ns]比object存字符串快 5–10 倍,且支持向量化时间操作 - 别依赖
parse_dates,它内部仍先读成object再转,多占一倍内存
复杂查询优先在 SQLite 端聚合,别把原始大表拖进 Pandas
“先读全表再 groupby” 是新手典型陷阱。10GB 的原始表,聚合后可能只剩几 MB —— 这部分计算应该由 SQLite 完成。
例如统计每日订单量:
# ❌ 错误:加载全部订单再算
df = pd.read_sql_query("SELECT * FROM orders", conn)
result = df.groupby(df["created_at"].dt.date).size()
<h1>✅ 正确:SQLite 完成聚合,只传结果回 Pandas</h1><p>result_df = pd.read_sql_query("""
SELECT DATE(created_at) as date, COUNT(*) as cnt
FROM orders
GROUP BY DATE(created_at)
""", conn)</p>
- 加
WHERE过滤条件(如时间范围)永远放在 SQL 里,而不是读进来再df.query() - SQLite 支持大部分常用聚合函数(
SUM,AVG,MIN/MAX,COUNT,GROUP_CONCAT),善用它们 - 如果需要窗口函数(如
ROW_NUMBER()),确认 SQLite 版本 ≥ 3.25.0,并开启PRAGMA enable_window_functions = 1
实际处理时,最易被忽略的是 索引存在性验证 和 PRAGMA 配置持久化。建完索引要 EXPLAIN QUERY PLAN 确认它真被用了;journal_mode、cache_size 等设置每次连接都要重设,不能只在创建 DB 时配一次。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











