save code
This commit is contained in:
@@ -0,0 +1,789 @@
|
||||
# 后端数据库迁移文档
|
||||
|
||||
## 文档信息
|
||||
- **文档版本**: V1.0
|
||||
- **创建日期**: 2026-01-26
|
||||
- **文档类型**: 后端数据库迁移文档
|
||||
|
||||
---
|
||||
|
||||
## 1. 数据库迁移概述
|
||||
|
||||
### 1.1 什么是数据库迁移
|
||||
|
||||
数据库迁移是一种管理数据库模式变更的方法,它允许您:
|
||||
|
||||
- 版本化数据库模式
|
||||
- 跟踪数据库变更历史
|
||||
- 在不同环境之间一致地应用数据库变更
|
||||
- 回滚错误的数据库变更
|
||||
- 协作开发数据库模式
|
||||
|
||||
### 1.2 为什么使用数据库迁移
|
||||
|
||||
- **可追溯性**: 记录所有数据库变更的历史
|
||||
- **一致性**: 在开发、测试和生产环境中应用相同的变更
|
||||
- **安全性**: 提供回滚机制,防止错误变更导致的数据丢失
|
||||
- **协作**: 团队成员可以共享和审查数据库变更
|
||||
- **自动化**: 简化数据库部署流程
|
||||
|
||||
---
|
||||
|
||||
## 2. 迁移工具
|
||||
|
||||
### 2.1 Alembic
|
||||
|
||||
本项目使用 **Alembic** 作为数据库迁移工具。Alembic 是 SQLAlchemy 的官方数据库迁移工具,提供了以下功能:
|
||||
|
||||
- 自动生成迁移脚本
|
||||
- 管理迁移版本
|
||||
- 应用和回滚迁移
|
||||
- 支持多种数据库
|
||||
- 与 SQLAlchemy 无缝集成
|
||||
|
||||
### 2.2 安装 Alembic
|
||||
|
||||
Alembic 已经在项目依赖中包含,使用以下命令安装:
|
||||
|
||||
```bash
|
||||
# 使用 Poetry
|
||||
poetry install
|
||||
|
||||
# 使用 pip
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 迁移配置
|
||||
|
||||
### 3.1 初始化 Alembic
|
||||
|
||||
如果项目还没有初始化 Alembic,使用以下命令:
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行
|
||||
alembic init alembic
|
||||
```
|
||||
|
||||
### 3.2 配置文件
|
||||
|
||||
Alembic 的主要配置文件是 `alembic.ini`,位于项目根目录:
|
||||
|
||||
```ini
|
||||
# alembic.ini
|
||||
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
script_location = alembic
|
||||
|
||||
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
||||
# Uncomment the line below if you want the files to be prepended with date and time
|
||||
# file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
||||
|
||||
# sys.path path, will be prepended to sys.path if present.
|
||||
# defaults to the current working directory.
|
||||
prepend_sys_path = .
|
||||
|
||||
# timezone to use when rendering the date within the migration file
|
||||
# as well as the filename.
|
||||
# If specified, requires the python-dateutil library
|
||||
# timezone =
|
||||
|
||||
# max length of characters to apply to the
|
||||
# "slug" field
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
# set to 'true' to allow .pyc and .pyo files without
|
||||
# a source .py file to be detected as revisions in the
|
||||
# versions/ directory
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; This defaults
|
||||
# to alembic/versions. When using multiple version
|
||||
# directories, initial revisions must be specified with --version-path.
|
||||
# The path separator used here should be the separator specified by "version_path_separator" below.
|
||||
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
|
||||
|
||||
# version path separator; As mentioned above, this is the character used to split
|
||||
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
||||
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
||||
# Valid values for version_path_separator are:
|
||||
#
|
||||
# version_path_separator = :
|
||||
# version_path_separator = ;
|
||||
# version_path_separator = space
|
||||
version_path_separator = os # Use os.pathsep.
|
||||
# the output encoding used when revision files
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
sqlalchemy.url = mysql+pymysql://username:password@localhost:3306/project_management
|
||||
|
||||
|
||||
[post_write_hooks]
|
||||
# post_write_hooks defines scripts or Python functions that are run
|
||||
# on newly generated revision scripts. See the documentation for further
|
||||
# detail and examples
|
||||
|
||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||
# hooks = black
|
||||
# black.type = console_scripts
|
||||
# black.entrypoint = black
|
||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
||||
```
|
||||
|
||||
### 3.3 环境配置
|
||||
|
||||
Alembic 的环境配置文件是 `alembic/env.py`,用于配置数据库连接和迁移行为:
|
||||
|
||||
```python
|
||||
# alembic/env.py
|
||||
|
||||
from logging.config import fileConfig
|
||||
from sqlalchemy import engine_from_config
|
||||
from sqlalchemy import pool
|
||||
from alembic import context
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 将项目根目录添加到 Python 路径
|
||||
sys.path.append(str(Path(__file__).parent.parent))
|
||||
|
||||
# 导入模型和配置
|
||||
from app.config import settings
|
||||
from app.database.base import Base
|
||||
from app.models import * # 导入所有模型
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# 使用环境变量中的数据库 URL
|
||||
config.set_main_option('sqlalchemy.url', settings.database_url)
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
if config.config_file_name is not None:
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
target_metadata = Base.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
# my_important_option = config.get_main_option("my_important_option")
|
||||
# ... etc.
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL
|
||||
and not an Engine, though an Engine is acceptable
|
||||
here as well. By skipping the Engine creation
|
||||
we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the
|
||||
script output.
|
||||
|
||||
"""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
connectable = engine_from_config(
|
||||
config.get_section(config.config_ini_section, {}),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(
|
||||
connection=connection, target_metadata=target_metadata
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
```
|
||||
|
||||
### 3.4 脚本模板
|
||||
|
||||
Alembic 使用 `script.py.mako` 作为迁移脚本的模板,位于 `alembic` 目录:
|
||||
|
||||
```mako
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
branch_labels = ${repr(branch_labels)}
|
||||
depends_on = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
${downgrades if downgrades else "pass"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 迁移操作
|
||||
|
||||
### 4.1 生成迁移脚本
|
||||
|
||||
#### 自动生成迁移脚本
|
||||
|
||||
当您修改了模型定义后,使用以下命令自动生成迁移脚本:
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行
|
||||
alembic revision --autogenerate -m "描述迁移内容"
|
||||
|
||||
# 示例
|
||||
alembic revision --autogenerate -m "创建用户表"
|
||||
```
|
||||
|
||||
#### 手动创建迁移脚本
|
||||
|
||||
如果需要手动创建迁移脚本,使用以下命令:
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行
|
||||
alembic revision -m "描述迁移内容"
|
||||
|
||||
# 示例
|
||||
alembic revision -m "添加用户状态字段"
|
||||
```
|
||||
|
||||
然后编辑生成的迁移脚本,添加具体的迁移操作。
|
||||
|
||||
### 4.2 应用迁移
|
||||
|
||||
#### 应用所有迁移
|
||||
|
||||
使用以下命令将所有未应用的迁移应用到数据库:
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
#### 应用特定迁移
|
||||
|
||||
使用以下命令将迁移应用到特定版本:
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行
|
||||
alembic upgrade <revision_id>
|
||||
|
||||
# 示例
|
||||
alembic upgrade 1234abcd
|
||||
```
|
||||
|
||||
### 4.3 回滚迁移
|
||||
|
||||
#### 回滚到上一个版本
|
||||
|
||||
使用以下命令回滚到上一个迁移版本:
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行
|
||||
alembic downgrade -1
|
||||
```
|
||||
|
||||
#### 回滚到特定版本
|
||||
|
||||
使用以下命令回滚到特定迁移版本:
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行
|
||||
alembic downgrade <revision_id>
|
||||
|
||||
# 示例
|
||||
alembic downgrade 1234abcd
|
||||
```
|
||||
|
||||
#### 回滚到初始状态
|
||||
|
||||
使用以下命令回滚到初始状态:
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行
|
||||
alembic downgrade base
|
||||
```
|
||||
|
||||
### 4.4 查看迁移状态
|
||||
|
||||
使用以下命令查看当前的迁移状态:
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行
|
||||
alembic current
|
||||
```
|
||||
|
||||
使用以下命令查看所有迁移版本:
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行
|
||||
alembic history
|
||||
|
||||
# 显示更详细的信息
|
||||
alembic history --verbose
|
||||
|
||||
# 显示图形化的迁移树
|
||||
alembic history --graph
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 迁移最佳实践
|
||||
|
||||
### 5.1 迁移脚本管理
|
||||
|
||||
1. **清晰的迁移消息**: 使用描述性的迁移消息,说明迁移的目的
|
||||
2. **版本控制**: 将迁移脚本纳入版本控制
|
||||
3. **测试迁移**: 在应用到生产环境之前,在测试环境中测试迁移
|
||||
4. **备份数据**: 在应用迁移之前,备份数据库
|
||||
5. **逐步迁移**: 对于大型迁移,考虑分步骤进行
|
||||
|
||||
### 5.2 迁移脚本编写
|
||||
|
||||
1. **保持迁移脚本简单**: 每个迁移脚本只包含一个逻辑变更
|
||||
2. **处理默认值**: 为新添加的列提供合理的默认值
|
||||
3. **处理数据迁移**: 如果需要迁移数据,在迁移脚本中添加相应的代码
|
||||
4. **处理约束**: 注意外键约束和唯一约束的处理
|
||||
5. **编写回滚脚本**: 确保每个迁移都有对应的回滚操作
|
||||
|
||||
### 5.3 常见迁移操作
|
||||
|
||||
#### 添加表
|
||||
|
||||
```python
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
'users',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('username', sa.String(length=50), nullable=False),
|
||||
sa.Column('password', sa.String(length=128), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('username')
|
||||
)
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table('users')
|
||||
```
|
||||
|
||||
#### 添加列
|
||||
|
||||
```python
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
'users',
|
||||
sa.Column('email', sa.String(length=100), nullable=True)
|
||||
)
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column('users', 'email')
|
||||
```
|
||||
|
||||
#### 修改列
|
||||
|
||||
```python
|
||||
def upgrade() -> None:
|
||||
op.alter_column(
|
||||
'users',
|
||||
'email',
|
||||
existing_type=sa.String(length=100),
|
||||
nullable=False
|
||||
)
|
||||
|
||||
def downgrade() -> None:
|
||||
op.alter_column(
|
||||
'users',
|
||||
'email',
|
||||
existing_type=sa.String(length=100),
|
||||
nullable=True
|
||||
)
|
||||
```
|
||||
|
||||
#### 添加索引
|
||||
|
||||
```python
|
||||
def upgrade() -> None:
|
||||
op.create_index(
|
||||
op.f('ix_users_email'),
|
||||
'users',
|
||||
['email'],
|
||||
unique=True
|
||||
)
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index(op.f('ix_users_email'), table_name='users')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据库初始化
|
||||
|
||||
### 6.1 初始迁移
|
||||
|
||||
当创建新项目时,需要执行初始迁移:
|
||||
|
||||
1. **创建模型**: 定义所有数据库模型
|
||||
2. **生成初始迁移**: `alembic revision --autogenerate -m "初始迁移"`
|
||||
3. **应用初始迁移**: `alembic upgrade head`
|
||||
|
||||
### 6.2 数据种子
|
||||
|
||||
在应用初始迁移后,可能需要添加一些初始数据,例如管理员用户:
|
||||
|
||||
```python
|
||||
# 在 app/database/seed.py 中
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from app.models.user import User
|
||||
from app.database.session import get_db
|
||||
from passlib.context import CryptContext
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
def seed_data(db: Session) -> None:
|
||||
"""添加初始数据"""
|
||||
# 检查是否已有管理员用户
|
||||
admin_user = db.query(User).filter(User.username == "admin").first()
|
||||
if not admin_user:
|
||||
# 创建管理员用户
|
||||
admin_user = User(
|
||||
username="admin",
|
||||
password=pwd_context.hash("123456"),
|
||||
real_name="系统管理员",
|
||||
department="ADMIN",
|
||||
role="ADMIN",
|
||||
status=1
|
||||
)
|
||||
db.add(admin_user)
|
||||
|
||||
# 检查是否已有市场部用户
|
||||
marketing_user = db.query(User).filter(User.username == "marketing").first()
|
||||
if not marketing_user:
|
||||
# 创建市场部用户
|
||||
marketing_user = User(
|
||||
username="marketing",
|
||||
password=pwd_context.hash("123456"),
|
||||
real_name="市场部经理",
|
||||
department="MARKETING",
|
||||
role="MARKETING",
|
||||
status=1
|
||||
)
|
||||
db.add(marketing_user)
|
||||
|
||||
# 检查是否已有其他部门用户
|
||||
other_user = db.query(User).filter(User.username == "other").first()
|
||||
if not other_user:
|
||||
# 创建其他部门用户
|
||||
other_user = User(
|
||||
username="other",
|
||||
password=pwd_context.hash("123456"),
|
||||
real_name="技术部经理",
|
||||
department="TECHNOLOGY",
|
||||
role="OTHER",
|
||||
status=1
|
||||
)
|
||||
db.add(other_user)
|
||||
|
||||
db.commit()
|
||||
|
||||
if __name__ == "__main__":
|
||||
db = next(get_db())
|
||||
try:
|
||||
seed_data(db)
|
||||
print("数据种子添加成功")
|
||||
finally:
|
||||
db.close()
|
||||
```
|
||||
|
||||
执行数据种子脚本:
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行
|
||||
python -m app.database.seed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 迁移示例
|
||||
|
||||
### 7.1 示例 1: 创建用户表
|
||||
|
||||
**迁移脚本**: `alembic/versions/1234abcd_create_user_table.py`
|
||||
|
||||
```python
|
||||
"""创建用户表
|
||||
|
||||
Revision ID: 1234abcd
|
||||
Revises:
|
||||
Create Date: 2025-01-01 00:00:00.000000
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '1234abcd'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table('sys_user',
|
||||
sa.Column('user_id', sa.String(length=32), nullable=False),
|
||||
sa.Column('username', sa.String(length=50), nullable=False),
|
||||
sa.Column('password', sa.String(length=128), nullable=False),
|
||||
sa.Column('real_name', sa.String(length=50), nullable=False),
|
||||
sa.Column('department', sa.String(length=50), nullable=False),
|
||||
sa.Column('phone', sa.String(length=20), nullable=True),
|
||||
sa.Column('email', sa.String(length=100), nullable=True),
|
||||
sa.Column('role', sa.String(length=20), nullable=False),
|
||||
sa.Column('status', sa.Integer(), nullable=False),
|
||||
sa.Column('create_time', sa.DateTime(), nullable=False),
|
||||
sa.Column('update_time', sa.DateTime(), nullable=False),
|
||||
sa.Column('last_login_time', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('user_id'),
|
||||
sa.UniqueConstraint('username')
|
||||
)
|
||||
op.create_index(op.f('ix_sys_user_department'), 'sys_user', ['department'], unique=False)
|
||||
op.create_index(op.f('ix_sys_user_role'), 'sys_user', ['role'], unique=False)
|
||||
op.create_index(op.f('ix_sys_user_status'), 'sys_user', ['status'], unique=False)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index(op.f('ix_sys_user_status'), table_name='sys_user')
|
||||
op.drop_index(op.f('ix_sys_user_role'), table_name='sys_user')
|
||||
op.drop_index(op.f('ix_sys_user_department'), table_name='sys_user')
|
||||
op.drop_table('sys_user')
|
||||
```
|
||||
|
||||
### 7.2 示例 2: 创建项目表
|
||||
|
||||
**迁移脚本**: `alembic/versions/5678efgh_create_project_table.py`
|
||||
|
||||
```python
|
||||
"""创建项目表
|
||||
|
||||
Revision ID: 5678efgh
|
||||
Revises: 1234abcd
|
||||
Create Date: 2025-01-02 00:00:00.000000
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '5678efgh'
|
||||
down_revision = '1234abcd'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table('project',
|
||||
sa.Column('project_id', sa.String(length=32), nullable=False),
|
||||
sa.Column('project_no', sa.String(length=20), nullable=False),
|
||||
sa.Column('project_name', sa.String(length=200), nullable=False),
|
||||
sa.Column('status', sa.String(length=20), nullable=False),
|
||||
sa.Column('create_time', sa.DateTime(), nullable=False),
|
||||
sa.Column('creator', sa.String(length=50), nullable=False),
|
||||
sa.Column('update_time', sa.DateTime(), nullable=False),
|
||||
sa.Column('last_modifier', sa.String(length=50), nullable=True),
|
||||
sa.Column('leader', sa.String(length=50), nullable=False),
|
||||
sa.Column('phone', sa.String(length=20), nullable=True),
|
||||
sa.Column('email', sa.String(length=100), nullable=True),
|
||||
sa.Column('background', sa.Text(), nullable=True),
|
||||
sa.Column('goal', sa.Text(), nullable=True),
|
||||
sa.Column('scope', sa.Text(), nullable=True),
|
||||
sa.Column('start_date', sa.Date(), nullable=False),
|
||||
sa.Column('planned_end_date', sa.Date(), nullable=False),
|
||||
sa.Column('actual_end_date', sa.Date(), nullable=True),
|
||||
sa.Column('total_budget', sa.Numeric(precision=15, scale=2), nullable=False),
|
||||
sa.Column('used_budget', sa.Numeric(precision=15, scale=2), nullable=False),
|
||||
sa.Column('remaining_budget', sa.Numeric(precision=15, scale=2), nullable=False),
|
||||
sa.Column('remarks', sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('project_id'),
|
||||
sa.UniqueConstraint('project_no')
|
||||
)
|
||||
op.create_index(op.f('ix_project_create_time'), 'project', ['create_time'], unique=False)
|
||||
op.create_index(op.f('ix_project_creator'), 'project', ['creator'], unique=False)
|
||||
op.create_index(op.f('ix_project_leader'), 'project', ['leader'], unique=False)
|
||||
op.create_index(op.f('ix_project_status'), 'project', ['status'], unique=False)
|
||||
op.create_index(op.f('ix_project_update_time'), 'project', ['update_time'], unique=False)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index(op.f('ix_project_update_time'), table_name='project')
|
||||
op.drop_index(op.f('ix_project_status'), table_name='project')
|
||||
op.drop_index(op.f('ix_project_leader'), table_name='project')
|
||||
op.drop_index(op.f('ix_project_creator'), table_name='project')
|
||||
op.drop_index(op.f('ix_project_create_time'), table_name='project')
|
||||
op.drop_table('project')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 常见问题
|
||||
|
||||
### 8.1 迁移失败
|
||||
|
||||
**问题**:迁移应用失败,出现错误
|
||||
|
||||
**解决方法**:
|
||||
1. 查看错误信息,了解失败原因
|
||||
2. 检查迁移脚本是否正确
|
||||
3. 检查数据库连接是否正常
|
||||
4. 如果需要,回滚到之前的版本并修复问题
|
||||
|
||||
### 8.2 自动生成的迁移脚本不正确
|
||||
|
||||
**问题**:Alembic 自动生成的迁移脚本与预期不符
|
||||
|
||||
**解决方法**:
|
||||
1. 检查模型定义是否正确
|
||||
2. 手动编辑生成的迁移脚本
|
||||
3. 确保所有模型都已导入到 `env.py` 中
|
||||
|
||||
### 8.3 数据库连接错误
|
||||
|
||||
**问题**:无法连接到数据库进行迁移
|
||||
|
||||
**解决方法**:
|
||||
1. 检查数据库服务是否运行
|
||||
2. 检查数据库连接字符串是否正确
|
||||
3. 检查数据库用户权限是否正确
|
||||
|
||||
### 8.4 迁移版本冲突
|
||||
|
||||
**问题**:多个开发者创建了相同版本号的迁移
|
||||
|
||||
**解决方法**:
|
||||
1. 使用唯一的迁移版本号
|
||||
2. 在提交迁移脚本前检查版本冲突
|
||||
3. 如果发生冲突,重新生成迁移脚本
|
||||
|
||||
---
|
||||
|
||||
## 9. 附录
|
||||
|
||||
### 9.1 常用命令汇总
|
||||
|
||||
| 命令 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `alembic init` | 初始化 Alembic | `alembic init alembic` |
|
||||
| `alembic revision --autogenerate` | 自动生成迁移脚本 | `alembic revision --autogenerate -m "创建用户表"` |
|
||||
| `alembic revision` | 手动创建迁移脚本 | `alembic revision -m "添加用户状态字段"` |
|
||||
| `alembic upgrade head` | 应用所有迁移 | `alembic upgrade head` |
|
||||
| `alembic upgrade <revision>` | 应用到特定版本 | `alembic upgrade 1234abcd` |
|
||||
| `alembic downgrade -1` | 回滚到上一个版本 | `alembic downgrade -1` |
|
||||
| `alembic downgrade <revision>` | 回滚到特定版本 | `alembic downgrade 1234abcd` |
|
||||
| `alembic downgrade base` | 回滚到初始状态 | `alembic downgrade base` |
|
||||
| `alembic current` | 查看当前版本 | `alembic current` |
|
||||
| `alembic history` | 查看所有版本 | `alembic history` |
|
||||
| `alembic history --verbose` | 查看详细版本信息 | `alembic history --verbose` |
|
||||
| `alembic history --graph` | 查看迁移树 | `alembic history --graph` |
|
||||
|
||||
### 9.2 迁移最佳实践总结
|
||||
|
||||
1. **版本控制**: 将迁移脚本纳入版本控制
|
||||
2. **测试**: 在应用到生产环境之前测试迁移
|
||||
3. **备份**: 在应用迁移之前备份数据库
|
||||
4. **简单**: 每个迁移只包含一个逻辑变更
|
||||
5. **清晰**: 使用描述性的迁移消息
|
||||
6. **完整**: 编写正确的升级和降级脚本
|
||||
7. **协作**: 与团队成员协调迁移工作
|
||||
8. **监控**: 监控迁移执行情况
|
||||
|
||||
### 9.3 变更记录
|
||||
|
||||
| 版本 | 日期 | 修改人 | 修改内容 |
|
||||
|------|------|--------|----------|
|
||||
| V1.0 | 2026-01-26 | - | 初始版本创建 |
|
||||
Reference in New Issue
Block a user