← 中文资源

简体中文版本

AsyncPG COPY 批量装载

用 asyncpg 的 COPY 协议把海量行流式灌入 PostgreSQL:异步生成器供给数据、临时表暂存、单事务完成装载。

阅读英文原文

示例说明

逐行 INSERT 的往返开销在大批量面前不可接受,COPY 把整批数据编码成一条协议消息传输,速度差一到两个数量级。异步生成器作为数据源让装载与生产并发流水线化——外部数据分页读出时不必先全部驻留内存。临时表暂存后再原子切换,业务表不被半成品污染。

技术要点

  • COPY 单协议消息替代逐行往返
  • 异步生成器边产边装零全量驻留
  • 临时表隔离装载与业务可见性

代码解读

generate_rows 模拟两万五千行的异步供给;bulk_copy 在事务里先建临时表再 copy_to_table,生成器直接作为 source 传入;事务提交后临时表在会话内可见,取计数验证装载量,finally 里关闭连接释放服务端资源。

使用建议

临时表是会话级的,切正式表要在同一连接内完成。错误行处理可先 COPY 到带宽松约束的暂存再清洗。大装载注意目标库的 WAL 与磁盘压力。

代码示例

import asyncio

import asyncpg


async def generate_rows(n: int):
    for i in range(n):
        # 生产环境中这里通常是异步分页读取外部数据源
        yield (i, f'user-{i}', i % 2 == 0 and 'active' or 'inactive')


async def bulk_copy(dsn: str, rows, batch_size: int = 10_000) -> int:
    conn = await asyncpg.connect(dsn)
    loaded = 0
    try:
        async with conn.transaction():
            await conn.execute(
                'CREATE TEMP TABLE staging_users '
                '(id bigint PRIMARY KEY, name text, status text)'
            )


            await conn.copy_to_table(
                'staging_users',
                source=rows,
            )
            loaded = await conn.fetchval('SELECT count(*) FROM staging_users')
    finally:
        await conn.close()
    return loaded


async def main() -> None:
    rows = generate_rows(25_000)
    total = await bulk_copy(
        'postgresql://user:pass@localhost/app',
        rows,
    )
    print('rows staged:', total)


asyncio.run(main())