限制和注意事项¶
本文档说明 SQLAlchemy CouchDB Dialect 的当前限制和注意事项。
SQL 功能限制¶
Phase 1 不支持的功能¶
Phase 1(纯 CouchDB 模式)目前不支持以下 SQL 功能:
1. JOIN 操作¶
限制: CouchDB 不支持多表联接。
替代方案:
# ❌ 不支持
SELECT u.name, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
# ✅ 方案 1: 客户端联接
users = conn.execute("SELECT * FROM users").fetchall()
orders = conn.execute("SELECT * FROM orders").fetchall()
# 在 Python 中进行联接
for user in users:
user_orders = [o for o in orders if o.user_id == user.id]
# 处理结果
# ✅ 方案 2: 文档嵌套(推荐)
# 将订单嵌入用户文档
user_doc = {
"_id": "user:1",
"name": "Alice",
"orders": [
{"id": "order:1", "total": 100},
{"id": "order:2", "total": 200}
]
}
# ✅ 方案 3: Phase 2 混合架构
# 复杂查询自动路由到 RDBMS
engine = create_engine(
'couchdb+hybrid://admin:password@localhost:5984/mydb'
'?secondary_db=postgresql://user:pass@localhost/pgdb'
)
2. 子查询¶
限制: 不支持嵌套 SELECT。
替代方案:
# ❌ 不支持
SELECT * FROM users WHERE age > (
SELECT AVG(age) FROM users
)
# ✅ 分步查询
avg_age_result = conn.execute("SELECT age FROM users").fetchall()
avg_age = sum(r.age for r in avg_age_result) / len(avg_age_result)
users = conn.execute(
"SELECT * FROM users WHERE age > :avg",
{"avg": avg_age}
).fetchall()
# ✅ 使用 CouchDB 视图(高级)
from sqlalchemy_couchdb.management import ViewManager
view_manager = ViewManager(client)
view_manager.create_view(
'mydb',
'stats',
'average_age',
map_function="""function(doc) {
if (doc.type === 'users') {
emit(null, doc.age);
}
}""",
reduce_function="_stats"
)
3. GROUP BY(部分支持)¶
限制: 基础 GROUP BY 需要客户端处理或 CouchDB 视图。
支持情况:
- ✅ 简单聚合(COUNT, SUM, AVG, MIN, MAX)- 使用 advanced.py
- ✅ 单字段 GROUP BY - 使用 QueryProcessor
- ❌ 复杂 GROUP BY(多字段、HAVING)- 需要 Phase 2
示例:
from sqlalchemy_couchdb.advanced import QueryProcessor
# 获取原始数据
result = conn.execute("SELECT * FROM orders").fetchall()
# 客户端分组
processor = QueryProcessor(result)
grouped = processor.group_by(
['user_id'],
{'total': 'sum', 'order_id': 'count'}
)
for group in grouped:
print(f"用户 {group['user_id']}: 总金额 {group['total_sum']}, 订单数 {group['order_id_count']}")
4. UNION/INTERSECT/EXCEPT¶
限制: 不支持集合操作。
替代方案:
# ❌ 不支持
SELECT id FROM users
UNION
SELECT id FROM customers
# ✅ 客户端合并
users = set(r.id for r in conn.execute("SELECT id FROM users"))
customers = set(r.id for r in conn.execute("SELECT id FROM customers"))
# UNION
all_ids = users.union(customers)
# INTERSECT
common_ids = users.intersection(customers)
# EXCEPT
only_users = users.difference(customers)
5. 窗口函数¶
限制: 不支持 ROW_NUMBER(), RANK(), LEAD(), LAG() 等。
替代方案: 客户端处理或使用 Phase 2。
6. 事务(Transaction)¶
限制: CouchDB 不支持多文档 ACID 事务。
特点:
- ✅ 单文档操作是原子的
- ✅ 批量操作(bulk_docs)保证全部成功或全部���败
- ❌ 跨文档事务需要应用层处理
- ❌ ROLLBACK 是 no-op(无操作)
最佳实践:
# ✅ 使用批量操作保证原子性
from sqlalchemy import insert
stmt = insert(users_table)
conn.execute(stmt, [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
]) # 全部成功或全部失败
# ✅ 单文档操作是原子的
conn.execute(
"UPDATE users SET balance = balance - 100 WHERE _id = :id",
{"id": "user:1"}
)
# ❌ 跨文档事务不保证
# 需要应用层实现补偿逻辑
try:
conn.execute("UPDATE account_a SET balance = balance - 100")
conn.execute("UPDATE account_b SET balance = balance + 100")
except Exception as e:
# 需要手动回滚 account_a
pass
CouchDB 特定限制¶
1. _rev 版本控制¶
要求: UPDATE/DELETE 必须提供正确的 _rev。
# ✅ 正确的更新流程
# 1. 先查询获取 _rev
result = conn.execute(
"SELECT _id, _rev, name FROM users WHERE _id = :id",
{"id": "user:1"}
)
row = result.fetchone()
# 2. 使用 _rev 更新
conn.execute(
"UPDATE users SET name = :name WHERE _id = :id AND _rev = :rev",
{"id": row._id, "rev": row._rev, "name": "New Name"}
)
# ❌ 不提供 _rev 会失败
conn.execute(
"UPDATE users SET name = :name WHERE _id = :id",
{"id": "user:1", "name": "New Name"}
) # 错误:需要 _rev
冲突处理:
from sqlalchemy_couchdb.exceptions import OperationalError
try:
conn.execute(update_stmt)
except OperationalError as e:
if "409" in str(e): # Conflict
# 重新获取最新的 _rev 并重试
pass
2. 索引限制¶
Mango Query 索引要求: - ORDER BY 字段必须有索引 - 复杂查询可能需要复合索引 - 索引创建是异步的
自动索引:
# ✅ ORDER BY 会自动创建索引
conn.execute("SELECT * FROM users ORDER BY age") # 自动创建 age 索引
# ✅ 手动创建复合索引
from sqlalchemy_couchdb.management import IndexManager
index_mgr = IndexManager(client)
index_mgr.create_index(
'mydb',
fields=['age', 'name'],
index_name='idx_age_name'
)
3. 查询性能¶
限制: - 全表扫描性能较差 - 没有索引的查询会很慢 - LIKE 查询性能低于精确匹配
优化建议:
# ❌ 慢:全表扫描
SELECT * FROM users WHERE description LIKE '%keyword%'
# ✅ 快:使用索引
SELECT * FROM users WHERE status = 'active' AND age > 25
# ✅ 使用字段选择
SELECT _id, name FROM users # 只返回需要的字段
# ✅ 使用 LIMIT
SELECT * FROM users ORDER BY created_at DESC LIMIT 100
异步操作限制¶
1. 迭代限制¶
限制: 异步结果不支持 async for。
# ❌ 不支持
async for row in result: # 错误!
print(row)
# ✅ 使用同步迭代(greenlet 机制)
result = await conn.execute(stmt)
for row in result: # 正确
print(row)
# ✅ 或使用 fetch 方法
rows = result.fetchall()
for row in rows:
print(row)
原因: SQLAlchemy 2.0 的 greenlet 机制限制。
2. 并发限制¶
建议:
import asyncio
from sqlalchemy.ext.asyncio import create_async_engine
async def main():
engine = create_async_engine(
'couchdb+async://admin:password@localhost:5984/mydb',
pool_size=20, # 控制连接池大小
max_overflow=10
)
# ✅ 使用 Semaphore 限制并发
sem = asyncio.Semaphore(10) # 最多 10 个并发
async def query_with_limit(id):
async with sem:
async with engine.connect() as conn:
return await conn.execute(
text("SELECT * FROM users WHERE _id = :id"),
{"id": id}
)
# 并发查询
tasks = [query_with_limit(f"user:{i}") for i in range(100)]
results = await asyncio.gather(*tasks)
asyncio.run(main())
数据类型限制¶
1. CouchDB 原生类型¶
CouchDB 存储的是 JSON 文档,支持的类型有限:
| 支持 | 不支持 |
|---|---|
| String | Binary/BLOB |
| Number (int, float) | Decimal(精度) |
| Boolean | Enum |
| Date/DateTime(字符串) | Time |
| Object/Array | 自定义类型 |
| null | - |
解决方案:
# ✅ Decimal 转换为字符串存储
from decimal import Decimal
import json
class DecimalEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Decimal):
return str(obj)
return super().default(obj)
# ✅ Binary 数据使用附件
from sqlalchemy_couchdb.attachments import AttachmentManager
att_mgr = AttachmentManager(client)
att_mgr.upload_attachment(
doc_id="user:1",
filename="avatar.jpg",
content=image_bytes,
content_type="image/jpeg"
)
2. 字段名限制¶
保留字段:
- _id: 文档 ID(必需)
- _rev: 版本号(自动)
- type: 表名映射(推荐)
- _attachments: 附件(特殊)
- _conflicts: 冲突(特殊)
- _deleted: 删除标记(特殊)
命名建议:
- 避免以 _ 开头(保留给 CouchDB)
- 使用 snake_case
- 避免特殊字符
Phase 2 规划¶
以下限制将在 Phase 2(混合架构)中解决:
即将支持¶
- ✅ JOIN 操作(通过 RDBMS)
- ✅ 子查询(通过 RDBMS)
- ✅ 复杂 GROUP BY(通过 RDBMS)
- ✅ 窗口函数(通过 RDBMS)
- ✅ UNION/INTERSECT(通过 RDBMS)
- ✅ 完整的事务支持(通过 RDBMS)
混合架构工作原理¶
# 自动路由
engine = create_engine(
'couchdb+hybrid://admin:password@localhost:5984/mydb'
'?secondary_db=postgresql://user:pass@localhost/pgdb'
)
# 简单查询 → CouchDB
result = conn.execute("SELECT * FROM users WHERE age > 25")
# 复杂查询 → PostgreSQL
result = conn.execute("""
SELECT u.name, COUNT(o.id) as order_count
FROM users u
JOIN orders o ON u.id = o.user_id
GROUP BY u.name
HAVING COUNT(o.id) > 10
""") # 自动路由到 PostgreSQL
最佳实践¶
1. 选择合适的场景¶
适合使用 CouchDB: - ✅ 文档存储(用户资料、配置) - ✅ 键值查询(按 ID 查找) - ✅ 简单范围查询(年龄、日期) - ✅ 离线优先应用(复制) - ✅ 高可用性需求
不适合使用 CouchDB: - ❌ 复杂关系查询 - ❌ 多表联接 - ❌ 复杂聚合统计 - ❌ 强一致性事务 - ❌ 全文搜索(使用 Elasticsearch)
2. 文档设计¶
推荐:
# ✅ 嵌入相关数据
user_doc = {
"_id": "user:1",
"type": "users",
"name": "Alice",
"profile": {
"bio": "...",
"avatar_url": "..."
},
"recent_orders": [
{"id": "order:1", "total": 100},
{"id": "order:2", "total": 200}
]
}
# ✅ 使用有意义的 ID
# 格式: {type}:{unique_id}
"user:alice@example.com"
"order:2024-001"
"product:SKU-12345"
避免:
# ❌ 过度规范化
user_doc = {"_id": "user:1", "name": "Alice"}
profile_doc = {"_id": "profile:1", "user_id": "user:1", ...}
orders_doc = {"_id": "order:1", "user_id": "user:1", ...}
# 这需要多次查询!
# ✅ 合理嵌套
user_doc = {
"_id": "user:1",
"name": "Alice",
"profile": {...},
"recent_orders": [...] # 只保留最近的
}
3. 监控和调试¶
import logging
# 开启日志
logging.basicConfig(level=logging.DEBUG)
logging.getLogger('sqlalchemy_couchdb').setLevel(logging.DEBUG)
# 查看生成的 Mango Query
from sqlalchemy_couchdb.compiler import CouchDBCompiler
compiler = CouchDBCompiler(...)
mango_query = compiler.process(stmt)
print(json.dumps(mango_query, indent=2))
# 查看 CouchDB 查询统计
curl http://admin:password@localhost:5984/mydb/_index
获取帮助¶
如果遇到限制相关的问题:
提示: 许多限制将在 Phase 2 混合架构中解决。敬请期待!