You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5.2 KiB
5.2 KiB
AI Service 数据库迁移幂等性修复
日期: 2026-01-30
类型: 数据库迁移修复
影响范围: AI Service 模块
问题描述
AI Service 数据库迁移脚本 20260129_1800_create_ai_service_tables.py 存在幂等性问题:
- GIN 索引错误: JSONB 列的 GIN 索引创建失败,错误提示
operator class "jsonb_path_ops" does not accept data type json - 列类型不匹配: 数据库中
ai_usage_logs.extra_data列类型为json,但 Model 定义为extra_metadata: JSONB - 非幂等性: 使用
CREATE TABLE IF NOT EXISTS但后续的索引和注释语句未做条件检查
解决方案
1. 修复迁移脚本幂等性
修改文件: server/alembic/versions/20260129_1800_create_ai_service_tables.py
1.1 添加列类型检查和转换
为所有 JSONB 列添加类型检查,如果列类型为 json 则转换为 jsonb:
# ai_models 表
op.execute("""
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'ai_models'
AND column_name = 'config'
AND data_type = 'json'
) THEN
ALTER TABLE ai_models ALTER COLUMN config TYPE JSONB USING config::jsonb;
END IF;
END $$;
""")
1.2 条件化 GIN 索引创建
将 GIN 索引创建包装在条件检查中:
op.execute("""
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'ai_usage_logs'
AND column_name = 'extra_metadata'
) THEN
CREATE INDEX IF NOT EXISTS idx_ai_usage_logs_extra_metadata_gin
ON ai_usage_logs USING gin (extra_metadata jsonb_path_ops);
END IF;
END $$;
""")
1.3 条件化列注释
对可能不存在的列,使用动态 SQL 添加注释:
op.execute("""
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'ai_usage_logs'
AND column_name = 'extra_metadata'
) THEN
EXECUTE 'COMMENT ON COLUMN ai_usage_logs.extra_metadata IS ''使用元数据''';
END IF;
END $$;
""")
2. 修复数据库列名和类型不匹配
由于 SQLModel 自动创建的表使用了旧的列名 extra_data (类型 json),需要手动修复:
-- 重命名列
ALTER TABLE ai_usage_logs
RENAME COLUMN extra_data TO extra_metadata;
-- 转换类型
ALTER TABLE ai_usage_logs
ALTER COLUMN extra_metadata TYPE JSONB USING extra_metadata::jsonb;
-- 设置默认值
ALTER TABLE ai_usage_logs
ALTER COLUMN extra_metadata SET DEFAULT '{}'::jsonb;
-- 创建 GIN 索引
CREATE INDEX IF NOT EXISTS idx_ai_usage_logs_extra_metadata_gin
ON ai_usage_logs USING gin (extra_metadata jsonb_path_ops);
验证结果
表创建成功
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public' AND table_name LIKE 'ai_%';
结果:
- ✅
ai_models(16 列) - ✅
ai_jobs(21 列) - ✅
ai_usage_logs(10 列) - ✅
ai_quotas(9 列)
JSONB 列和索引验证
\d ai_models
\d ai_jobs
\d ai_usage_logs
确认:
- ✅
ai_models.config- JSONB 类型,带 GIN 索引 - ✅
ai_jobs.input_data- JSONB 类型,带 GIN 索引 - ✅
ai_jobs.output_data- JSONB 类型,带 GIN 索引 - ✅
ai_usage_logs.extra_metadata- JSONB 类型,带 GIN 索引
技术要点
PostgreSQL GIN 索引操作符类
PostgreSQL 的 GIN 索引需要指定正确的操作符类:
- 错误:
USING gin (config)- 对 JSONB 列会失败 - 正确:
USING gin (config jsonb_path_ops)- 使用jsonb_path_ops操作符类
jsonb_path_ops 操作符类:
- 仅支持
@>操作符(包含查询) - 索引更小,性能更好
- 适合大多数 JSONB 查询场景
迁移脚本幂等性最佳实践
- 表创建: 使用
CREATE TABLE IF NOT EXISTS - 列修改: 先检查列是否存在及其类型
- 索引创建: 使用
CREATE INDEX IF NOT EXISTS并检查列存在性 - 约束添加: 检查约束是否已存在
- 触发器创建: 使用
IF NOT EXISTS条件
SQLModel 自动表创建问题
SQLModel 会在应用启动时自动创建表,可能导致:
- 表结构与迁移脚本不一致
- 列名或类型不匹配
- 缺少索引或约束
解决方案:
- 禁用 SQLModel 自动表创建
- 完全依赖 Alembic 迁移管理表结构
- 或确保 Model 定义与迁移脚本完全一致
影响范围
- ✅ AI Service 所有表创建成功
- ✅ JSONB 列类型正确
- ✅ GIN 索引创建成功
- ✅ 迁移脚本完全幂等
- ✅ 可重复执行不会报错
相关文件
server/alembic/versions/20260129_1800_create_ai_service_tables.py- 迁移脚本server/app/models/ai_usage_log.py- AIUsageLog Model 定义server/app/models/ai_model.py- AIModel Model 定义server/app/models/ai_job.py- AIJob Model 定义server/app/models/ai_quota.py- AIQuota Model 定义
后续建议
- 禁用 SQLModel 自动表创建: 在生产环境中完全依赖 Alembic 管理表结构
- 迁移测试: 在 CI/CD 中添加迁移幂等性测试
- 文档更新: 更新数据库设计文档,明确 JSONB 列的索引策略