SqlStorage 数据库存储使用指南¶
本文档详细介绍了如何使用 SqlStorage 类进行数据库操作,包括 MySQL、PostgreSQL、SQLite 等数据库的支持。
概述¶
SqlStorage 是一个基于 SQLAlchemy 的异步/同步数据库存储实现,提供了统一的接口来处理各种 SQL 数据库操作。
核心组件¶
1. SqlStorage 类¶
主要的存储类,提供数据库连接和操作接口。
2. 辅助类¶
SqlKey: 用于标识数据库查询的键SqlCondition: 用于定义查询条件StorageData: 数据模型基类
前置条件¶
1. 安装必需的依赖¶
# 核心依赖
pip install sqlalchemy
# MySQL 支持
pip install aiomysql PyMySQL
# PostgreSQL 支持
pip install asyncpg psycopg2
# SQLite 支持(Python 内置)
# 无需额外安装
2. 数据库设置¶
MySQL 设置¶
-- 创建数据库
CREATE DATABASE test_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 创建用户(可选)
CREATE USER 'test_user'@'localhost' IDENTIFIED BY 'test_password';
GRANT ALL PRIVILEGES ON test_db.* TO 'test_user'@'localhost';
FLUSH PRIVILEGES;
PostgreSQL 设置¶
-- 创建数据库
CREATE DATABASE test_db;
-- 创建用户(可选)
CREATE USER test_user WITH PASSWORD 'test_password';
GRANT ALL PRIVILEGES ON DATABASE test_db TO test_user;
基本使用方法¶
1. 初始化 SqlStorage¶
from trpc_agent_sdk.storage import SqlStorage
# 异步模式(推荐)
storage = SqlStorage(
is_async=True,
db_url="mysql+aiomysql://root:password@localhost/test_db",
echo=True, # 启用 SQL 日志
pool_size=10,
max_overflow=20
)
# 同步模式
storage = SqlStorage(
is_async=False,
db_url="mysql+pymysql://root:password@localhost/test_db",
echo=True
)
2. 定义数据模型¶
from dataclasses import dataclass
from datetime import datetime
from sqlalchemy import Column, Integer, String, DateTime, Text
from trpc_agent_sdk.storage import StorageData
@dataclass
class UserData(StorageData):
"""用户数据模型"""
__tablename__ = 'users'
id: int = Column(Integer, primary_key=True, autoincrement=True)
username: str = Column(String(50), unique=True, nullable=False)
email: str = Column(String(100), nullable=False)
full_name: str = Column(String(100), nullable=True)
bio: str = Column(Text, nullable=True)
created_at: datetime = Column(DateTime, default=datetime.utcnow)
updated_at: datetime = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
3. 基本操作示例¶
import asyncio
from trpc_agent_sdk.storage import SqlStorage, SqlKey, SqlCondition
async def basic_example():
# 初始化存储
storage = SqlStorage(
is_async=True,
db_url="mysql+aiomysql://root:password@localhost/test_db"
)
try:
# 创建数据库引擎和表
await storage.create_sql_engine()
# 使用数据库会话
async with storage.create_db_session() as session:
# 创建新用户
user = UserData(
username="john_doe",
email="john@example.com",
full_name="John Doe",
bio="Software engineer"
)
# 添加用户
await storage.add(session, user)
await storage.commit(session)
await storage.refresh(session, user)
print(f"Created user with ID: {user.id}")
# 获取用户
user_key = SqlKey(key=(user.id,), storage_cls=UserData)
retrieved_user = await storage.get(session, user_key)
print(f"Retrieved user: {retrieved_user.username}")
# 查询用户
query_key = SqlKey(key=(), storage_cls=UserData)
condition = SqlCondition(
filters=[UserData.email.like('%@example.com')],
order_func=lambda: UserData.created_at.desc(),
limit=10
)
users = await storage.query(session, query_key, condition)
print(f"Found {len(users)} users")
finally:
await storage.close()
# 运行示例
asyncio.run(basic_example())
4. 配置管理¶
import os
from dataclasses import dataclass
from typing import Dict, Any
@dataclass
class DatabaseConfig:
"""数据库配置类"""
host: str = "localhost"
port: int = 3306
username: str = "root"
password: str = "password"
database: str = "test_db"
charset: str = "utf8mb4"
# 连接池设置
pool_size: int = 10
max_overflow: int = 20
pool_timeout: int = 30
pool_recycle: int = 3600
# SQLAlchemy 设置
echo: bool = False
echo_pool: bool = False
def get_async_url(self) -> str:
"""获取异步连接 URL"""
return f"mysql+aiomysql://{self.username}:{self.password}@{self.host}:{self.port}/{self.database}?charset={self.charset}"
def get_sync_url(self) -> str:
"""获取同步连接 URL"""
return f"mysql+pymysql://{self.username}:{self.password}@{self.host}:{self.port}/{self.database}?charset={self.charset}"
def get_engine_kwargs(self) -> Dict[str, Any]:
"""获取引擎参数"""
return {
"echo": self.echo,
"echo_pool": self.echo_pool,
"pool_size": self.pool_size,
"max_overflow": self.max_overflow,
"pool_timeout": self.pool_timeout,
"pool_recycle": self.pool_recycle,
}
@classmethod
def from_env(cls) -> 'DatabaseConfig':
"""从环境变量创建配置"""
return cls(
host=os.getenv("DB_HOST", "localhost"),
port=int(os.getenv("DB_PORT", "3306")),
username=os.getenv("DB_USER", "root"),
password=os.getenv("DB_PASSWORD", "password"),
database=os.getenv("DB_NAME", "test_db"),
echo=os.getenv("DB_ECHO", "false").lower() == "true",
)
SqlStorage 接口详解¶
1. 核心接口¶
数据库引擎管理¶
# 创建数据库引擎和表
await storage.create_sql_engine()
# 关闭数据库连接
await storage.close()
会话管理¶
# 创建数据库会话(推荐使用上下文管理器)
async with storage.create_db_session() as session:
# 在这里执行数据库操作
pass
# 创建原始会话(需要手动管理)
session = await storage.create_sql_session()
2. CRUD 操作¶
添加数据¶
async with storage.create_db_session() as session:
user = UserData(username="test", email="test@example.com")
await storage.add(session, user)
await storage.commit(session)
await storage.refresh(session, user) # 获取自动生成的 ID
获取数据¶
async with storage.create_db_session() as session:
# 通过主键获取
user_key = SqlKey(key=(user_id,), storage_cls=UserData)
user = await storage.get(session, user_key)
查询数据¶
async with storage.create_db_session() as session:
query_key = SqlKey(key=(), storage_cls=UserData)
# 简单查询
condition = SqlCondition()
all_users = await storage.query(session, query_key, condition)
# 带条件查询
condition = SqlCondition(
filters=[
UserData.email.like('%@example.com'),
UserData.created_at > datetime(2024, 1, 1)
],
order_func=lambda: UserData.created_at.desc(),
limit=10
)
filtered_users = await storage.query(session, query_key, condition)
删除数据¶
async with storage.create_db_session() as session:
delete_key = SqlKey(key=(), storage_cls=UserData)
condition = SqlCondition(filters=[UserData.id == user_id])
await storage.delete(session, delete_key, condition)
await storage.commit(session)
更新数据¶
async with storage.create_db_session() as session:
user_key = SqlKey(key=(user_id,), storage_cls=UserData)
user = await storage.get(session, user_key)
if user:
user.bio = "Updated bio"
user.updated_at = datetime.utcnow()
await storage.commit(session)
await storage.refresh(session, user)
3. 高级功能¶
事务管理¶
async with storage.create_db_session() as session:
try:
# 执行多个操作
await storage.add(session, user1)
await storage.add(session, user2)
await storage.commit(session)
except Exception as e:
# 自动回滚(由上下文管理器处理)
print(f"Transaction failed: {e}")
raise
批量操作¶
async with storage.create_db_session() as session:
users = [
UserData(username=f"user_{i}", email=f"user_{i}@example.com")
for i in range(10)
]
for user in users:
await storage.add(session, user)
await storage.commit(session)
复杂查询条件¶
from sqlalchemy import and_, or_
condition = SqlCondition(
filters=[
and_(
UserData.created_at > datetime(2024, 1, 1),
or_(
UserData.email.like('%@gmail.com'),
UserData.email.like('%@yahoo.com')
)
)
],
order_func=lambda: [UserData.created_at.desc(), UserData.username.asc()],
limit=50
)
数据库连接 URL¶
MySQL¶
# 异步连接(推荐)
mysql_async_url = "mysql+aiomysql://username:password@host:port/database"
# 同步连接
mysql_sync_url = "mysql+pymysql://username:password@host:port/database"
# 带参数的连接
mysql_url = "mysql+aiomysql://user:pass@localhost/db?charset=utf8mb4&autocommit=true"
# SSL 连接
mysql_ssl_url = "mysql+pymysql://user:pass@host/db?ssl_ca=/path/to/ca.pem"
PostgreSQL¶
# 异步连接
postgres_async_url = "postgresql+asyncpg://username:password@host:port/database"
# 同步连接
postgres_sync_url = "postgresql+psycopg2://username:password@host:port/database"
# 带参数的连接
postgres_url = "postgresql+asyncpg://user:pass@localhost/db?ssl=require"
SQLite¶
# 异步连接
sqlite_async_url = "sqlite+aiosqlite:///path/to/database.db"
# 同步连接
sqlite_sync_url = "sqlite:///path/to/database.db"
# 内存数据库
sqlite_memory_url = "sqlite:///:memory:"
数据模型定义¶
基本模型¶
from dataclasses import dataclass
from datetime import datetime
from sqlalchemy import Column, Integer, String, DateTime, Text, Boolean, ForeignKey
from sqlalchemy.orm import relationship
from trpc_agent_sdk.storage import StorageData
@dataclass
class UserData(StorageData):
"""用户数据模型"""
__tablename__ = 'users'
id: int = Column(Integer, primary_key=True, autoincrement=True)
username: str = Column(String(50), unique=True, nullable=False, index=True)
email: str = Column(String(100), nullable=False, index=True)
full_name: str = Column(String(100), nullable=True)
bio: str = Column(Text, nullable=True)
is_active: bool = Column(Boolean, default=True)
created_at: datetime = Column(DateTime, default=datetime.utcnow, index=True)
updated_at: datetime = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
关联模型¶
@dataclass
class PostData(StorageData):
"""文章数据模型"""
__tablename__ = 'posts'
id: int = Column(Integer, primary_key=True, autoincrement=True)
title: str = Column(String(200), nullable=False)
content: str = Column(Text, nullable=False)
user_id: int = Column(Integer, ForeignKey('users.id'), nullable=False)
created_at: datetime = Column(DateTime, default=datetime.utcnow)
# 关联关系(可选)
# user = relationship("UserData", back_populates="posts")
@dataclass
class TagData(StorageData):
"""标签数据模型"""
__tablename__ = 'tags'
id: int = Column(Integer, primary_key=True, autoincrement=True)
name: str = Column(String(50), unique=True, nullable=False)
description: str = Column(String(200), nullable=True)
created_at: datetime = Column(DateTime, default=datetime.utcnow)
模型最佳实践¶
@dataclass
class BaseModel(StorageData):
"""基础模型类"""
__abstract__ = True # 抽象基类,不会创建表
id: int = Column(Integer, primary_key=True, autoincrement=True)
created_at: datetime = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at: datetime = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
@dataclass
class ProductData(BaseModel):
"""产品数据模型"""
__tablename__ = 'products'
name: str = Column(String(100), nullable=False, index=True)
price: int = Column(Integer, nullable=False) # 以分为单位存储
description: str = Column(Text, nullable=True)
is_available: bool = Column(Boolean, default=True, index=True)
category_id: int = Column(Integer, ForeignKey('categories.id'), nullable=True)
完整使用示例¶
实际应用示例¶
import asyncio
from datetime import datetime
from trpc_agent_sdk.storage import SqlStorage, SqlKey, SqlCondition
class UserService:
"""用户服务类示例"""
def __init__(self, db_url: str):
self.storage = SqlStorage(
is_async=True,
db_url=db_url,
echo=True,
pool_size=10,
max_overflow=20
)
async def initialize(self):
"""初始化数据库"""
await self.storage.create_sql_engine()
async def create_user(self, username: str, email: str, full_name: str = None) -> int:
"""创建用户"""
async with self.storage.create_db_session() as session:
user = UserData(
username=username,
email=email,
full_name=full_name,
created_at=datetime.utcnow()
)
await self.storage.add(session, user)
await self.storage.commit(session)
await self.storage.refresh(session, user)
return user.id
async def get_user_by_id(self, user_id: int) -> UserData:
"""根据ID获取用户"""
async with self.storage.create_db_session() as session:
user_key = SqlKey(key=(user_id,), storage_cls=UserData)
return await self.storage.get(session, user_key)
async def find_users_by_email_domain(self, domain: str, limit: int = 10) -> list:
"""根据邮箱域名查找用户"""
async with self.storage.create_db_session() as session:
query_key = SqlKey(key=(), storage_cls=UserData)
condition = SqlCondition(
filters=[UserData.email.like(f'%@{domain}')],
order_func=lambda: UserData.created_at.desc(),
limit=limit
)
return await self.storage.query(session, query_key, condition)
async def update_user_bio(self, user_id: int, bio: str) -> bool:
"""更新用户简介"""
async with self.storage.create_db_session() as session:
user_key = SqlKey(key=(user_id,), storage_cls=UserData)
user = await self.storage.get(session, user_key)
if user:
user.bio = bio
user.updated_at = datetime.utcnow()
await self.storage.commit(session)
return True
return False
async def delete_inactive_users(self, days: int = 30) -> int:
"""删除非活跃用户"""
from datetime import timedelta
cutoff_date = datetime.utcnow() - timedelta(days=days)
async with self.storage.create_db_session() as session:
delete_key = SqlKey(key=(), storage_cls=UserData)
condition = SqlCondition(
filters=[
UserData.is_active == False,
UserData.updated_at < cutoff_date
]
)
# 先查询要删除的用户数量
users_to_delete = await self.storage.query(session, delete_key, condition)
count = len(users_to_delete)
# 执行删除
await self.storage.delete(session, delete_key, condition)
await self.storage.commit(session)
return count
async def close(self):
"""关闭数据库连接"""
await self.storage.close()
# 使用示例
async def main():
service = UserService("mysql+aiomysql://root:password@localhost/test_db")
try:
await service.initialize()
# 创建用户
user_id = await service.create_user("john_doe", "john@example.com", "John Doe")
print(f"Created user with ID: {user_id}")
# 获取用户
user = await service.get_user_by_id(user_id)
print(f"Retrieved user: {user.username}")
# 查找用户
users = await service.find_users_by_email_domain("example.com")
print(f"Found {len(users)} users with example.com email")
# 更新用户
success = await service.update_user_bio(user_id, "Updated bio")
print(f"Update successful: {success}")
finally:
await service.close()
if __name__ == "__main__":
asyncio.run(main())
错误处理和最佳实践¶
1. 异常处理¶
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
async def safe_create_user(storage, username: str, email: str):
"""安全创建用户的示例"""
async with storage.create_db_session() as session:
try:
user = UserData(username=username, email=email)
await storage.add(session, user)
await storage.commit(session)
await storage.refresh(session, user)
return user.id
except IntegrityError as e:
print(f"数据完整性错误(可能是重复数据): {e}")
return None
except SQLAlchemyError as e:
print(f"数据库错误: {e}")
return None
except Exception as e:
print(f"未知错误: {e}")
return None
2. 连接管理¶
class DatabaseManager:
"""数据库管理器"""
def __init__(self, db_url: str):
self.storage = None
self.db_url = db_url
async def __aenter__(self):
self.storage = SqlStorage(is_async=True, db_url=self.db_url)
await self.storage.create_sql_engine()
return self.storage
async def __aexit__(self, exc_type, exc_val, exc_tb):
if self.storage:
await self.storage.close()
# 使用方式
async def example_with_manager():
async with DatabaseManager("mysql+aiomysql://...") as storage:
async with storage.create_db_session() as session:
# 执行数据库操作
pass
3. 性能优化建议¶
连接池配置¶
storage = SqlStorage(
is_async=True,
db_url=db_url,
pool_size=20, # 连接池大小
max_overflow=30, # 最大溢出连接数
pool_timeout=30, # 获取连接超时时间
pool_recycle=3600, # 连接回收时间(秒)
pool_pre_ping=True, # 连接前测试
)
批量操作¶
async def batch_create_users(storage, users_data: list):
"""批量创建用户"""
async with storage.create_db_session() as session:
try:
for user_data in users_data:
user = UserData(**user_data)
await storage.add(session, user)
await storage.commit(session)
print(f"Successfully created {len(users_data)} users")
except Exception as e:
print(f"Batch operation failed: {e}")
# 会话会自动回滚
查询优化¶
# 使用索引字段进行查询
condition = SqlCondition(
filters=[
UserData.username == "john_doe", # username 有索引
UserData.is_active == True # is_active 有索引
]
)
# 限制查询结果数量
condition = SqlCondition(
filters=[UserData.created_at > datetime(2024, 1, 1)],
order_func=lambda: UserData.created_at.desc(),
limit=100 # 限制结果数量
)
故障排除¶
常见错误及解决方案¶
- 连接错误 ```python # 错误: Can't connect to MySQL server # 解决: 检查数据库服务状态和连接参数
# 测试连接 try: await storage.create_sql_engine() print("数据库连接成功") except Exception as e: print(f"连接失败: {e}") ```
- 表不存在错误 ```python # 错误: Table 'database.table_name' doesn't exist # 解决: 确保调用了 create_sql_engine()
await storage.create_sql_engine() # 这会创建所有表 ```
- 数据完整性错误 ```python # 错误: Duplicate entry 'value' for key 'column_name' # 解决: 检查唯一约束字段
try: await storage.add(session, user) await storage.commit(session) except IntegrityError: print("数据已存在或违反约束条件") ```
调试模式¶
# 启用详细日志
import logging
logging.basicConfig(level=logging.DEBUG)
storage = SqlStorage(
is_async=True,
db_url=db_url,
echo=True, # 显示 SQL 语句
echo_pool=True, # 显示连接池信息
)
环境设置¶
# 安装依赖
pip install sqlalchemy aiomysql
# 设置环境变量
export DB_URL="mysql+aiomysql://root:password@localhost/test_db"
# 使用不同数据库
export DB_URL="postgresql+asyncpg://user:pass@localhost/test_db"
可以支持的数据库如下¶
PostgreSQL¶
- psycopg2
txt 必须安装包:pip install psycopg2-binary url: "postgresql+psycopg2://username:password@localhost:5432/mydb" - asyncpg
txt 必须安装包:pip install asyncpg url: "postgresql+asyncpg://username:password@localhost:5432/mydb" - pg8000
txt 必须安装包:pip install pg8000 url: "postgresql+pg8000://username:password@localhost:5432/mydb"
MySQL/MariaDB¶
- PyMySQL
txt 必须安装包:pip install PyMySQL url: "mysql+pymysql://username:password@localhost:3306/mydb" - mysqlclient
txt 必须安装包:pip install mysqlclient url: "mysql+mysqldb://username:password@localhost:3306/mydb" - mysqlconnector
txt 必须安装包:pip install mysql-connector-python url: "mysql+mysqlconnector://username:password@localhost:3306/mydb" - aiomysql
txt 必须安装包:pip install aiomysql url: "mysql+aiomysql://username:password@localhost:3306/mydb"
SQLite¶
- sqlite3
txt # 系统自带,无需安装 url: "sqlite:///./test.db" - aiosqlite
txt 必须安装包:pip install aiosqlite url: "sqlite+aiosqlite:///./test.db"
Oracle¶
- cx_Oracle
txt 必须安装包:pip install cx_Oracle url: "oracle+cx_oracle://username:password@localhost:1521/xe" - oracledb
txt 必须安装包:pip install oracledb url: "oracle+oracledb://username:password@localhost:1521/xe"
SQL Server¶
- pyodbc
txt 必须安装包:pip install pyodbc url: "mssql+pyodbc://username:password@server:1433/database?driver=ODBC+Driver+17+for+SQL+Server" - pymssql
txt 必须安装包:pip install pymssql url: "mssql+pymssql://username:password@server:1433/database"