SQLAlchemy读取SQLite海量数据速度慢于pandas/csv的原因及优化咨询
SQLAlchemy读取SQLite海量数据速度慢于pandas/csv的原因及优化咨询
兄弟,我太懂你这种想把海量数据快速拉进内存的急切感了!先把你遇到的测试情况理清楚:
测试数据:1000万行、10列的本地SQLite数据库,已基于datetime字符串和symbol建立索引,所有查询仅使用这两个索引,且愿意牺牲写入速度换取更快的读取速度
测试结果:
- SQLAlchemy ORM查询 + fetchall:耗时10分钟
- pd.readsql:耗时1分钟
- 直接读取同数据的CSV:耗时18秒
为什么速度差这么大?
- SQLAlchemy ORM的开销是重灾区:如果你用的是SQLAlchemy的ORM模式(比如
session.query(YourModel).all()),每一行数据都会被转换成一个Python对象,要做属性绑定、实例化等操作,这在百万级别的数据量下,Python层面的开销会被无限放大,这就是纯ORM fetchall慢到离谱的核心原因。 - pandas的底层优化buff拉满:
pd.readsql并没有走ORM的对象映射,而是直接把数据库返回的结果集转换成pandas的底层数组结构(C语言实现),绕开了Python对象创建的巨大开销,而且pandas内部做了很多批量处理的优化,比纯Python层面的循环高效得多。 - CSV的无额外开销优势:CSV是纯文本行式存储,读取时不需要解析SQL语句、不需要和数据库引擎交互,也没有查询计划、事务、锁这些数据库层面的额外开销,再加上pandas读CSV用的是高度优化的C引擎,自然速度最快。
针对你的需求的优化方案
1. 用SQLAlchemy Core替代ORM
如果你还是想基于SQLAlchemy操作,别用ORM的查询方式,改用SQLAlchemy Core(核心层),直接执行原生查询,避免对象映射的开销:
from sqlalchemy import create_engine, select, Table, MetaData engine = create_engine('sqlite:///your_database.db') metadata = MetaData() your_table = Table('your_table_name', metadata, autoload_with=engine) with engine.connect() as conn: # 直接执行全表查询(或你的过滤查询) stmt = select(your_table) result = conn.execute(stmt) # 直接获取原始结果集,比ORM的all()快N倍 raw_data = result.fetchall()
2. 给SQLite引擎做极致优化(牺牲写速度/部分安全性换读速)
既然你明确说愿意牺牲写入速度,那可以给SQLite开启这些优化参数:
- 开启WAL模式:提升读写并发和整体性能,尤其是读取时的效率
with engine.connect() as conn: conn.execute("PRAGMA journal_mode = WAL;") conn.commit() - 关闭同步模式:放弃数据库的写入同步校验,大幅提升读写速度,但如果突然断电可能丢失未提交的数据
with engine.connect() as conn: conn.execute("PRAGMA synchronous = OFF;") conn.commit() - 增大内存缓存:让SQLite把更多数据缓存到内存,减少磁盘IO
with engine.connect() as conn: # 这里设置的是200MB缓存(负号表示单位是MB) conn.execute("PRAGMA cache_size = -200000;") conn.commit() - 优化索引(针对过滤查询场景):你用datetime字符串做索引,建议把字段类型改成SQLite的
DATETIME,字符串索引的匹配和扫描开销比日期类型大很多;如果是按这两个字段过滤读取,创建覆盖索引(包含查询所需的所有列),这样数据库不用回表查主数据,直接从索引读取:
(把col1、col2换成你需要读取的所有列)CREATE INDEX idx_covering ON your_table (datetime_col, symbol, col1, col2, ...);
3. 继续用pandas做读取(兼顾速度和便捷性)
既然pd.readsql已经比纯SQLAlchemy快10倍,那可以继续用它,甚至还能再优化:
- 直接传SQLite连接字符串给pandas,跳过SQLAlchemy引擎的部分开销:
import pandas as pd df = pd.read_sql("SELECT * FROM your_table", "sqlite:///your_database.db") - 如果内存吃紧,可以用
chunksize分块读取,但你要全量进内存的话,直接读就行。
4. 终极备选:预导出为CSV
如果你的数据更新不频繁,完全可以定期把数据库里的数据导出成CSV,之后直接读CSV,速度能达到最快,毕竟你已经看到CSV的速度是碾压级的。
备注:内容来源于stack exchange,提问作者Martin Brennan
相关产品推荐
相关产品推荐

