modify: 建表冲突wt
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
# chongming-database
|
||||
|
||||
异步 SQLite 数据库封装,基于 `aiosqlite` + `SQLModel`(SQLAlchemy),提供简洁的初始化与会话管理接口。
|
||||
|
||||
---
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
uv pip install -e utils/database
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
### `Database` 类
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
from chongming_database import Database
|
||||
|
||||
# 创建实例
|
||||
db = Database(Path("data/storage/chongming/app.db"))
|
||||
|
||||
# 初始化(创建引擎 + 自动建表)
|
||||
await db.initialize()
|
||||
|
||||
# 获取异步会话
|
||||
async with db.get_session() as session:
|
||||
# 使用 SQLModel / SQLAlchemy 进行查询
|
||||
result = await session.execute(...)
|
||||
...
|
||||
|
||||
# 关闭连接
|
||||
await db.dispose()
|
||||
```
|
||||
|
||||
### 方法说明
|
||||
|
||||
| 方法 | 签名 | 说明 |
|
||||
| ------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `__init__` | `(db_path: Path)` | 传入 SQLite 文件路径,自动创建父目录 |
|
||||
| `initialize` | `async () -> None` | 创建异步 SQLAlchemy 引擎(`sqlite+aiosqlite:///`),执行 `SQLModel.metadata.create_all` 自动建表,构建 `sessionmaker`(`expire_on_commit=False`) |
|
||||
| `get_session` | `() -> AsyncSession` | 返回一个新的异步会话。如果 `initialize()` 未调用则抛出 `RuntimeError` |
|
||||
| `dispose` | `async () -> None` | 关闭异步引擎并清除内部状态 |
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 配合 SQLModel 定义表
|
||||
|
||||
```python
|
||||
from sqlmodel import SQLModel, Field
|
||||
from datetime import datetime, timezone
|
||||
|
||||
class User(SQLModel, table=True):
|
||||
id: str = Field(primary_key=True)
|
||||
username: str = Field(max_length=50, unique=True, index=True)
|
||||
created_at: datetime = Field(
|
||||
default_factory=lambda: datetime.now(timezone.utc)
|
||||
)
|
||||
```
|
||||
|
||||
### 完整生命周期
|
||||
|
||||
```python
|
||||
from chongming_database import Database
|
||||
from pathlib import Path
|
||||
|
||||
db = Database(Path("./data/myapp.db"))
|
||||
await db.initialize()
|
||||
|
||||
try:
|
||||
async with db.get_session() as session:
|
||||
user = User(id="u1", username="john")
|
||||
session.add(user)
|
||||
await session.commit()
|
||||
finally:
|
||||
await db.dispose()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 依赖
|
||||
|
||||
- `aiosqlite` — 异步 SQLite 驱动
|
||||
- `sqlmodel` — SQLAlchemy + Pydantic 融合 ORM
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from pathlib import Path
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.exc import OperationalError
|
||||
from sqlmodel import SQLModel
|
||||
|
||||
class Database:
|
||||
@@ -11,14 +12,33 @@ class Database:
|
||||
self._db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
async def initialize(self):
|
||||
"""异步初始化,创建引擎并建立表"""
|
||||
"""异步初始化,创建引擎并建立表
|
||||
|
||||
注意:DDL(CREATE TABLE)在 SQLite 中会自动提交,无需包裹在事务中。
|
||||
使用 connect() 而非 begin() 可以避免事务失败状态导致的连锁异常。
|
||||
|
||||
checkfirst=True 会在建表前检查表是否已存在(通过 sqlite_master),
|
||||
大幅减少多进程并发启动时的 "table already exists" 冲突。
|
||||
即使 checkfirst 与实际 CREATE 之间存在竞态窗口,后续的 try/except
|
||||
也会兜底捕获漏网的 OperationalError。
|
||||
"""
|
||||
self._engine = create_async_engine(
|
||||
f"sqlite+aiosqlite:///{self._db_path}",
|
||||
echo=False,
|
||||
future=True,
|
||||
)
|
||||
async with self._engine.begin() as conn:
|
||||
await conn.run_sync(SQLModel.metadata.create_all)
|
||||
async with self._engine.connect() as conn:
|
||||
try:
|
||||
await conn.run_sync(SQLModel.metadata.create_all, checkfirst=True)
|
||||
except OperationalError as e:
|
||||
# 多进程并发启动时,checkfirst 与实际 CREATE 之间仍有极小的竞态窗口:
|
||||
# 多个进程同时判断"表不存在"并执行 CREATE TABLE,
|
||||
# 后到者会报 "table ... already exists"。此时表已由其他进程建好,忽略即可。
|
||||
#
|
||||
# 注意:使用 connect() 而非 begin(),DDL 在 SQLite 中自动提交,
|
||||
# 无需手动 commit/rollback,避免事务失败状态的连锁异常。
|
||||
if "already exists" not in str(e).lower():
|
||||
raise
|
||||
self._session_maker = sessionmaker(
|
||||
self._engine, # type: ignore
|
||||
class_=AsyncSession,
|
||||
|
||||
Reference in New Issue
Block a user