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 pathlib import Path
|
||||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine, AsyncSession
|
from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine, AsyncSession
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from sqlalchemy.exc import OperationalError
|
||||||
from sqlmodel import SQLModel
|
from sqlmodel import SQLModel
|
||||||
|
|
||||||
class Database:
|
class Database:
|
||||||
@@ -11,14 +12,33 @@ class Database:
|
|||||||
self._db_path.parent.mkdir(parents=True, exist_ok=True)
|
self._db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
async def initialize(self):
|
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(
|
self._engine = create_async_engine(
|
||||||
f"sqlite+aiosqlite:///{self._db_path}",
|
f"sqlite+aiosqlite:///{self._db_path}",
|
||||||
echo=False,
|
echo=False,
|
||||||
future=True,
|
future=True,
|
||||||
)
|
)
|
||||||
async with self._engine.begin() as conn:
|
async with self._engine.connect() as conn:
|
||||||
await conn.run_sync(SQLModel.metadata.create_all)
|
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._session_maker = sessionmaker(
|
||||||
self._engine, # type: ignore
|
self._engine, # type: ignore
|
||||||
class_=AsyncSession,
|
class_=AsyncSession,
|
||||||
|
|||||||
Reference in New Issue
Block a user