asyncpg 全面教程:常用 API 串联与实战指南

大家好,我是jobleap.cn的小九。 asyncpg 是 Python 生态中高性能的异步 PostgreSQL 客户端库,原生支持 async/await 语法,相比基于同步库(如 psycopg2)封装的异步方案,性能提升显著,是异步 Python 项目操作 PostgreSQL 的首选。本教程将从环境准备、基础连接到高级特性(事务、连接池、批量操作等),全面串联 asyncpg 核心 API,并通过实战案例落地所有知识点。

一、环境准备

1. 安装依赖

bash 复制代码
pip install asyncpg  # 核心库
pip install python-dotenv  # 可选,管理数据库配置(推荐)

2. PostgreSQL 环境准备

先在 PostgreSQL 中创建测试数据库和表(可通过 psql 或 pgAdmin 执行):

sql 复制代码
-- 创建数据库
CREATE DATABASE test_db;

-- 切换到测试数据库
\c test_db;

-- 创建用户表(后续实战用)
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    age INT,
    profile JSONB,  -- PostgreSQL 原生 JSONB 类型
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

二、核心 API 详解与实战

1. 基础连接(单个连接)

asyncpg 最基础的用法是创建单个数据库连接,核心 API 是 asyncpg.connect()

示例代码:

python 复制代码
import asyncio
import asyncpg
from dotenv import load_dotenv  # 可选,推荐用.env管理配置
import os

# 加载环境变量(可选)
load_dotenv()
DB_CONFIG = {
    "user": os.getenv("DB_USER", "postgres"),
    "password": os.getenv("DB_PASSWORD", "123456"),
    "database": os.getenv("DB_NAME", "test_db"),
    "host": os.getenv("DB_HOST", "localhost"),
    "port": os.getenv("DB_PORT", 5432),
}

async def basic_connection():
    # 1. 建立连接
    conn = await asyncpg.connect(**DB_CONFIG)
    try:
        # 2. 执行简单查询(验证连接)
        version = await conn.fetchval("SELECT version();")
        print(f"PostgreSQL 版本: {version}")
    finally:
        # 3. 关闭连接(必须!否则会泄露连接)
        await conn.close()

if __name__ == "__main__":
    asyncio.run(basic_connection())

关键 API 说明:

  • asyncpg.connect(**kwargs):创建单个数据库连接,返回 Connection 对象;
  • conn.close():关闭连接,建议放在 finally 块中确保执行;
  • conn.fetchval(query):执行查询并返回单个值 (第一行第一列),适合 SELECT count(*), SELECT version() 等场景。

2. 基础 CRUD 操作

asyncpg 提供了三类核心查询 API 处理结果,覆盖所有 CRUD 场景:

API 用途 返回值
fetchval(query) 获取单个值(第一行第一列) 单个值(int/str/None等)
fetchrow(query) 获取单行结果 Record 对象
fetch(query) 获取多行结果 List[Record] 列表
execute(query) 执行写操作(INSERT/UPDATE/DELETE) 受影响的行数(int)

示例代码(完整 CRUD):

python 复制代码
async def crud_operations():
    conn = await asyncpg.connect(**DB_CONFIG)
    try:
        # ---------------------- 1. 插入(INSERT) ----------------------
        insert_sql = """
            INSERT INTO users (username, email, age, profile)
            VALUES ('zhangsan', 'zhangsan@example.com', 25, '{"hobby": "coding"}')
            RETURNING id;  -- 返回插入的ID(PostgreSQL 特性)
        """
        user_id = await conn.fetchval(insert_sql)
        print(f"插入用户成功,ID: {user_id}")

        # ---------------------- 2. 查询(SELECT) ----------------------
        # 单行查询
        single_user = await conn.fetchrow("SELECT * FROM users WHERE id = $1", user_id)
        print(f"单行用户数据: {single_user}")  # Record 对象,可通过键访问:single_user['username']
        
        # 多行查询
        all_users = await conn.fetch("SELECT username, email FROM users WHERE age > $1", 20)
        print("多行用户数据:")
        for user in all_users:
            print(f"用户名: {user['username']}, 邮箱: {user['email']}")

        # ---------------------- 3. 更新(UPDATE) ----------------------
        update_sql = "UPDATE users SET age = $1 WHERE id = $2"
        affected_rows = await conn.execute(update_sql, 26, user_id)
        print(f"更新受影响行数: {affected_rows}")  # 返回字符串如 "UPDATE 1",可转int

        # ---------------------- 4. 删除(DELETE) ----------------------
        delete_sql = "DELETE FROM users WHERE id = $1"
        affected_rows = await conn.execute(delete_sql, user_id)
        print(f"删除受影响行数: {affected_rows}")
    finally:
        await conn.close()

asyncio.run(crud_operations())

关键说明:

  • 参数化查询 :示例中用 $1, $2 作为参数占位符(asyncpg 标准),而非 %s,可防止 SQL 注入;
  • Record 对象:查询返回的结果对象,支持字典式访问(record['key'])、属性访问(record.key),也可通过 dict(record) 转换为普通字典。

3. 事务处理

PostgreSQL 事务遵循 ACID 原则,asyncpg 提供两种事务用法:手动事务上下文管理器事务(推荐)。

示例 1:上下文管理器事务(简洁安全)

python 复制代码
async def transaction_context_manager():
    conn = await asyncpg.connect(**DB_CONFIG)
    try:
        # 开启事务(async with 自动处理 commit/rollback)
        async with conn.transaction():
            # 批量插入两个用户
            await conn.execute(
                "INSERT INTO users (username, email) VALUES ($1, $2)",
                "lisi", "lisi@example.com"
            )
            await conn.execute(
                "INSERT INTO users (username, email) VALUES ($1, $2)",
                "wangwu", "wangwu@example.com"
            )
            # 模拟异常(取消注释测试回滚)
            # raise ValueError("模拟事务异常")
        print("事务提交成功!")
        
        # 验证结果
        count = await conn.fetchval("SELECT COUNT(*) FROM users")
        print(f"当前用户总数: {count}")
    except Exception as e:
        print(f"事务回滚,原因: {e}")
    finally:
        await conn.close()

asyncio.run(transaction_context_manager())

示例 2:手动事务(灵活控制)

python 复制代码
async def manual_transaction():
    conn = await asyncpg.connect(**DB_CONFIG)
    try:
        # 1. 开启事务
        tx = await conn.transaction()
        try:
            await conn.execute("INSERT INTO users (username, email) VALUES ($1, $2)", "zhaoliu", "zhaoliu@example.com")
            # 2. 提交事务
            await tx.commit()
            print("手动事务提交成功")
        except Exception as e:
            # 3. 回滚事务
            await tx.rollback()
            print(f"手动事务回滚,原因: {e}")
    finally:
        await conn.close()

asyncio.run(manual_transaction())

关键 API 说明:

  • conn.transaction():创建 Transaction 对象,开启事务;
  • 上下文管理器模式:async with 块内的操作要么全部成功(自动 commit),要么全部失败(自动 rollback);
  • 手动事务:通过 tx.commit() 提交、tx.rollback() 回滚,适合需要分步控制的场景。

4. 连接池(生产环境首选)

单个连接无法应对高并发场景,asyncpg 提供 asyncpg.create_pool() 创建连接池,自动管理连接的创建、复用和销毁。

示例代码:

python 复制代码
async def pool_operations():
    # 1. 创建连接池(建议全局唯一,复用)
    pool = await asyncpg.create_pool(**DB_CONFIG, min_size=5, max_size=20)
    try:
        # 2. 从池获取连接(两种方式)
        # 方式1:手动获取/释放
        conn = await pool.acquire()
        try:
            await conn.execute("INSERT INTO users (username, email) VALUES ($1, $2)", "pool_test", "pool@example.com")
        finally:
            # 释放连接回池(必须!否则池会耗尽)
            await pool.release(conn)

        # 方式2:上下文管理器(推荐,自动释放)
        async with pool.acquire() as conn:
            user = await conn.fetchrow("SELECT * FROM users WHERE username = $1", "pool_test")
            print(f"连接池查询结果: {user}")

        # 3. 批量执行(池直接执行,无需手动获取连接)
        await pool.execute("DELETE FROM users WHERE username = $1", "pool_test")
    finally:
        # 4. 关闭连接池(程序退出时执行)
        await pool.close()

asyncio.run(pool_operations())

连接池核心参数:

  • min_size:池的最小连接数(默认 10);
  • max_size:池的最大连接数(默认 10);
  • timeout:获取连接的超时时间(默认 30 秒)。

生产建议:

  • 连接池应全局初始化一次,而非每次请求创建;
  • 优先使用 async with pool.acquire() 语法,避免忘记释放连接。

5. 预处理语句(提升重复查询性能)

对于频繁执行的查询,asyncpg 支持预处理语句(Prepared Statements),只需编译一次,重复执行时跳过解析/优化步骤,大幅提升性能。

示例代码:

python 复制代码
async def prepared_statements():
    conn = await asyncpg.connect(**DB_CONFIG)
    try:
        # 1. 预处理语句(编译一次)
        stmt = await conn.prepare("SELECT * FROM users WHERE age > $1")
        
        # 2. 重复执行(复用编译结果)
        for age in [20, 25, 30]:
            users = await stmt.fetch(age)
            print(f"年龄大于 {age} 的用户: {len(users)}")
    finally:
        await conn.close()

asyncio.run(prepared_statements())

6. 批量操作(高效插入/更新)

6.1 executemany(批量插入)

适合中小批量数据插入:

python 复制代码
async def batch_insert_executemany():
    conn = await asyncpg.connect(**DB_CONFIG)
    try:
        # 批量数据
        users_data = [
            ("user1", "user1@example.com", 22),
            ("user2", "user2@example.com", 23),
            ("user3", "user3@example.com", 24),
        ]
        # 批量执行
        await conn.executemany(
            "INSERT INTO users (username, email, age) VALUES ($1, $2, $3)",
            users_data
        )
        print(f"批量插入 {len(users_data)} 条数据成功")
    finally:
        await conn.close()

asyncio.run(batch_insert_executemany())

6.2 copy_records_to_table(超大批量插入,性能最优)

针对万级/十万级数据插入,asyncpg 提供 copy_records_to_table,基于 PostgreSQL 的 COPY 协议,性能远超 executemany

python 复制代码
async def batch_insert_copy():
    conn = await asyncpg.connect(**DB_CONFIG)
    try:
        # 超大批量数据(示例1000条)
        large_data = [
            (f"batch_user{i}", f"batch_user{i}@example.com", 20 + i % 10)
            for i in range(1000)
        ]
        # 高效批量插入
        await conn.copy_records_to_table(
            "users",
            columns=["username", "email", "age"],
            records=large_data
        )
        print(f"COPY 批量插入 {len(large_data)} 条数据成功")
    finally:
        await conn.close()

asyncio.run(batch_insert_copy())

7. 自定义类型处理(JSONB 示例)

asyncpg 原生支持 PostgreSQL 特殊类型(如 JSONB、ARRAY、UUID),无需额外转换:

python 复制代码
async def jsonb_operations():
    conn = await asyncpg.connect(**DB_CONFIG)
    try:
        # 插入 JSONB 数据(直接传 Python 字典)
        profile = {"hobby": ["reading", "running"], "city": "Beijing"}
        await conn.execute(
            "INSERT INTO users (username, email, profile) VALUES ($1, $2, $3)",
            "json_user", "json@example.com", profile
        )

        # 查询 JSONB 数据(自动转为 Python 字典)
        user = await conn.fetchrow("SELECT * FROM users WHERE username = $1", "json_user")
        print(f"JSONB 数据: {user['profile']}")
        print(f"用户爱好: {user['profile']['hobby'][0]}")

        # JSONB 条件查询(PostgreSQL 原生语法)
        filtered = await conn.fetch(
            "SELECT username FROM users WHERE profile ->> 'city' = $1",
            "Beijing"
        )
        print(f"北京用户: {[u['username'] for u in filtered]}")
    finally:
        await conn.close()

asyncio.run(jsonb_operations())

8. 错误处理

asyncpg 抛出的异常均继承自 asyncpg.PostgresError,常见异常包括:

  • asyncpg.UniqueViolationError:唯一键冲突;
  • asyncpg.ForeignKeyViolationError:外键冲突;
  • asyncpg.PostgresSyntaxError:SQL 语法错误;
  • asyncpg.ConnectionFailureError:连接失败。

示例代码:

python 复制代码
async def error_handling():
    conn = await asyncpg.connect(**DB_CONFIG)
    try:
        # 故意插入重复用户名(触发唯一键冲突)
        await conn.execute(
            "INSERT INTO users (username, email) VALUES ($1, $2)",
            "json_user", "duplicate@example.com"
        )
    except asyncpg.UniqueViolationError as e:
        print(f"唯一键冲突错误: {e}")
    except asyncpg.PostgresError as e:
        print(f"PostgreSQL 通用错误: {e}")
    except Exception as e:
        print(f"其他错误: {e}")
    finally:
        await conn.close()

asyncio.run(error_handling())

三、实战案例:串联所有 API 实现用户管理

以下案例整合连接池、事务、参数化查询、批量操作、错误处理等所有核心 API,实现一个完整的异步用户管理功能:

python 复制代码
import asyncio
import asyncpg
import os
from dotenv import load_dotenv

load_dotenv()
DB_CONFIG = {
    "user": os.getenv("DB_USER", "postgres"),
    "password": os.getenv("DB_PASSWORD", "123456"),
    "database": os.getenv("DB_NAME", "test_db"),
    "host": os.getenv("DB_HOST", "localhost"),
    "port": os.getenv("DB_PORT", 5432),
}

# 全局连接池(生产环境建议单例)
global_pool = None

async def init_pool():
    """初始化连接池"""
    global global_pool
    global_pool = await asyncpg.create_pool(**DB_CONFIG, min_size=5, max_size=20)
    print("连接池初始化成功")

async def close_pool():
    """关闭连接池"""
    if global_pool:
        await global_pool.close()
        print("连接池已关闭")

async def create_user(username: str, email: str, age: int = None, profile: dict = None):
    """创建单个用户(带事务和错误处理)"""
    if not global_pool:
        raise RuntimeError("连接池未初始化")
    
    try:
        async with global_pool.acquire() as conn:
            async with conn.transaction():
                user_id = await conn.fetchval(
                    """
                    INSERT INTO users (username, email, age, profile)
                    VALUES ($1, $2, $3, $4)
                    RETURNING id
                    """,
                    username, email, age, profile
                )
                return user_id
    except asyncpg.UniqueViolationError:
        raise ValueError(f"用户名 {username} 或邮箱 {email} 已存在")
    except Exception as e:
        raise RuntimeError(f"创建用户失败: {e}")

async def batch_create_users(users_list: list):
    """批量创建用户(基于 COPY 协议)"""
    if not global_pool:
        raise RuntimeError("连接池未初始化")
    
    try:
        async with global_pool.acquire() as conn:
            await conn.copy_records_to_table(
                "users",
                columns=["username", "email", "age", "profile"],
                records=users_list
            )
        return len(users_list)
    except Exception as e:
        raise RuntimeError(f"批量创建用户失败: {e}")

async def get_user_by_id(user_id: int):
    """根据ID查询用户"""
    if not global_pool:
        raise RuntimeError("连接池未初始化")
    
    async with global_pool.acquire() as conn:
        user = await conn.fetchrow("SELECT * FROM users WHERE id = $1", user_id)
        return dict(user) if user else None

async def update_user_age(user_id: int, new_age: int):
    """更新用户年龄"""
    if not global_pool:
        raise RuntimeError("连接池未初始化")
    
    async with global_pool.acquire() as conn:
        affected = await conn.execute("UPDATE users SET age = $1 WHERE id = $2", new_age, user_id)
        return int(affected.split()[1])  # 转换 "UPDATE 1" 为 1

async def delete_user(user_id: int):
    """删除用户"""
    if not global_pool:
        raise RuntimeError("连接池未初始化")
    
    async with global_pool.acquire() as conn:
        affected = await conn.execute("DELETE FROM users WHERE id = $1", user_id)
        return int(affected.split()[1])

async def main():
    """主函数:串联所有操作"""
    # 1. 初始化连接池
    await init_pool()

    try:
        # 2. 创建单个用户
        user_id = await create_user(
            username="final_test",
            email="final@example.com",
            age=30,
            profile={"hobby": "swimming", "city": "Shanghai"}
        )
        print(f"创建单个用户成功,ID: {user_id}")

        # 3. 查询用户
        user = await get_user_by_id(user_id)
        print(f"查询用户: {user}")

        # 4. 更新用户年龄
        update_count = await update_user_age(user_id, 31)
        print(f"更新用户年龄,受影响行数: {update_count}")

        # 5. 批量创建用户
        batch_data = [
            ("batch_1", "batch1@example.com", 25, {"city": "Guangzhou"}),
            ("batch_2", "batch2@example.com", 26, {"city": "Shenzhen"}),
        ]
        batch_count = await batch_create_users(batch_data)
        print(f"批量创建 {batch_count} 个用户成功")

        # 6. 删除用户
        delete_count = await delete_user(user_id)
        print(f"删除用户,受影响行数: {delete_count}")

    except Exception as e:
        print(f"操作失败: {e}")
    finally:
        # 7. 关闭连接池
        await close_pool()

if __name__ == "__main__":
    asyncio.run(main())

四、总结与最佳实践

1. 核心 API 总结

场景 核心 API
连接管理 asyncpg.connect(), asyncpg.create_pool()
查询结果 fetchval(), fetchrow(), fetch()
写操作 execute(), executemany()
超大批量插入 copy_records_to_table()
事务 conn.transaction()(上下文管理器)
预处理语句 conn.prepare()

2. 最佳实践

  1. 生产环境优先用连接池 :单个连接无法应对并发,连接池需控制 max_size(建议不超过 PostgreSQL 最大连接数的 80%);
  2. 参数化查询必用 :避免字符串拼接 SQL,防止注入,统一用 $1, $2 占位符;
  3. 事务包裹写操作:INSERT/UPDATE/DELETE 建议放在事务中,确保数据一致性;
  4. 及时释放资源 :连接池的连接需通过 pool.release() 或上下文管理器释放,池需在程序退出时关闭;
  5. 错误处理精细化:针对不同 PostgreSQL 异常(如唯一键冲突)做针对性处理,提升用户体验;
  6. 超大批量用 COPY :数据量 > 1000 条时,优先用 copy_records_to_table() 而非 executemany()

通过本教程的 API 详解和实战案例,你已掌握 asyncpg 所有核心用法,可直接应用于异步 Python 项目(如 FastAPI、Sanic 等)的 PostgreSQL 操作场景。

相关推荐
武子康2 小时前
大数据-176 Elasticsearch Filter DSL 全面实战:过滤查询、排序分页、高亮与批量操作
大数据·后端·elasticsearch
Dream it possible!2 小时前
LeetCode 面试经典 150_图的广度优先搜索_蛇梯棋(93_909_C++_中等)(广度优选搜索)
c++·leetcode·面试·广度优先
kevinzeng2 小时前
SpringBoot自动装配注解
spring boot·后端
闲人编程2 小时前
GraphQL与REST API对比与实践
后端·python·api·graphql·rest·codecapsule
JavaEdge在掘金2 小时前
零距离拆解银行司库系统(TMS)的微服务设计与实践
后端
11来了2 小时前
DeepResearch 核心原理
后端
踏浪无痕2 小时前
三周手撸企业级认证系统(二) Spring Security + JWT 完整实战
spring boot·面试·架构
Wzx1980123 小时前
go接受输入方式
开发语言·后端·golang