大家好,我是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. 最佳实践
- 生产环境优先用连接池 :单个连接无法应对并发,连接池需控制
max_size(建议不超过 PostgreSQL 最大连接数的 80%); - 参数化查询必用 :避免字符串拼接 SQL,防止注入,统一用
$1,$2占位符; - 事务包裹写操作:INSERT/UPDATE/DELETE 建议放在事务中,确保数据一致性;
- 及时释放资源 :连接池的连接需通过
pool.release()或上下文管理器释放,池需在程序退出时关闭; - 错误处理精细化:针对不同 PostgreSQL 异常(如唯一键冲突)做针对性处理,提升用户体验;
- 超大批量用 COPY :数据量 > 1000 条时,优先用
copy_records_to_table()而非executemany()。
通过本教程的 API 详解和实战案例,你已掌握 asyncpg 所有核心用法,可直接应用于异步 Python 项目(如 FastAPI、Sanic 等)的 PostgreSQL 操作场景。