You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Polars流式读取数据库并控制读取批次大小?

Polars流式读取大体积数据库数据的解决方案

问题背景

我正在学习用Polars替代Pandas,但遇到了一个问题:如何让Polars流式读取数据库,或者控制每次读取的数据量?我的数据量很大,无法一次性加载到内存,必须分批次读取。尝试用pl.read_database的batch_size参数时,发现它还是会把全部数据加载到内存里;另外用connectorx配合pl.read_database_uri也没法实现流式读取,请问有没有解决办法?

我的环境配置:

[tool.poetry.dependencies]
python = "^3.11"
connectorx = "^0.3.2"
polars = {extras = ["connectorx", "pyarrow", "xlsx2csv"], version = "^0.19.3"}
sqlalchemy = "^2.0.21"
pymysql = "^1.1.0"
notebook = "^7.0.4"

我已经实现了一种可行的流式读取方式(基于SQLAlchemy):

# 用SQLAlchemy创建engine
with engine.connect() as conn:
    query = text('select * from my_table')
    # 开启流式结果,逐批获取数据
    with conn.execution_options(stream_results=True).execute(query) as cursor:
        while True:
             res = cursor.fetchmany(chunk_size)
             if not res:
                 break
             df = pl.DataFrame(res)
             # 在这里处理当前批次的df
             process_batch(df)

但我想知道怎么用pl.read_database或pl.read_database_uri结合batch_size参数实现类似的流式读取?

我尝试过以下代码,但它会一次性加载全部数据,达不到流式效果:

cursor = conn.cursor()

query = 'select * from users'

df = pl.read_database(query=query, connection=cursor, batch_size=1000)
for i in df.groupby(by='age'):
    print(type(i))

核心问题说明

pl.read_database的batch_size参数并不是用来实现流式迭代的——它只是控制connectorx在读取数据时的内部批次大小,最终还是会把所有批次合并成一个完整的DataFrame返回,所以无法实现分批次加载到内存的效果。

解决方案1:SQLAlchemy流式查询+Polars逐批转换

你已有的SQLAlchemy流式代码是可行的,这里优化成更贴合Polars的写法:

from sqlalchemy import create_engine, text
import polars as pl

engine = create_engine("mysql+pymysql://user:password@host:port/db_name")
chunk_size = 1000

with engine.connect() as conn:
    result = conn.execution_options(stream_results=True).execute(text("SELECT * FROM my_table"))
    # 逐批获取并转换为Polars DataFrame
    while batch := result.fetchmany(chunk_size):
        # 直接用查询结果的字段名作为Schema,避免列名丢失
        df_batch = pl.DataFrame(batch, schema=result.keys())
        # 处理当前批次数据(示例:分组统计、写入文件等)
        print(f"处理批次,行数:{len(df_batch)}")
        batch_stats = df_batch.group_by("age").agg(pl.count("id").alias("count"))
        print(batch_stats)

解决方案2:用pl.scan_database实现惰性加载(推荐)

Polars 0.19+版本支持pl.scan_database,它采用惰性查询模式,不会立即加载全量数据,而是按需获取。结合fetch_size和iter_batches可以轻松实现流式处理:

import polars as pl
from sqlalchemy import create_engine

engine = create_engine("mysql+pymysql://user:password@host:port/db_name")

# 创建惰性查询计划,fetch_size控制每次从数据库拉取的行数
lazy_df = pl.scan_database(
    query="SELECT * FROM my_table",
    connection=engine,
    fetch_size=1000
)

# 方式1:直接将数据分批写入文件(无需手动迭代)
lazy_df.sink_parquet("output.parquet", batch_size=1000)

# 方式2:手动迭代批次处理
for batch in lazy_df.iter_batches(batch_size=1000):
    print(f"批次行数:{len(batch)}")
    # 自定义批次处理逻辑
    process_batch(batch)

注意:scan_database依赖SQLAlchemy,不支持connectorx后端,需确保Polars已安装sqlalchemy相关依赖。

关于connectorx的限制

pl.read_database_uri基于connectorx实现时,目前不支持流式读取——connectorx会一次性把全量数据加载到内存再转换为Polars DataFrame,不适合超大规模数据场景。如果必须使用connectorx,只能通过SQL分页手动分批次查询,但这种方式效率较低,且可能存在数据一致性问题(查询过程中数据更新会导致重复或遗漏):

import polars as pl

batch_size = 1000
offset = 0
# 先获取总行数
total_rows = pl.read_database_uri(
    "SELECT COUNT(*) FROM my_table",
    uri="mysql://user:password@host:port/db_name"
).item()

while offset < total_rows:
    query = f"SELECT * FROM my_table LIMIT {batch_size} OFFSET {offset}"
    df_batch = pl.read_database_uri(query, uri="mysql://user:password@host:port/db_name")
    process_batch(df_batch)
    offset += batch_size

内容的提问来源于stack exchange,提问作者Jerry wang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 21:44:51