1. 项目概述:双数据库架构的必要性与挑战
在现代应用开发中,数据库选型往往面临两难抉择。以我们团队开发的HagiCode平台为例,这个基于ASP.NET Core和React的AI辅助开发系统,在数据库选择上遇到了典型困境:开发阶段需要轻量便捷的SQLite实现快速迭代,而生产环境则依赖PostgreSQL的强大性能处理高并发请求。
这种需求差异催生了双数据库架构方案。SQLite作为嵌入式数据库,无需服务端进程,单个文件即可运行,极大简化了开发环境配置。而PostgreSQL则以其ACID兼容性、丰富的数据类型和出色的并发处理能力,成为生产环境的理想选择。两者结合既能提升开发效率,又能保障线上稳定性。
2. 核心架构设计:抽象与实现的分离
2.1 统一接口设计原则
实现双数据库支持的核心在于"依赖倒置"原则。我们不应让业务代码直接依赖具体数据库实现,而是通过抽象接口进行交互。在.NET Core中,DbContext本身就是很好的抽象层,关键在于如何动态配置其底层实现。
public abstract class BaseDbContext : DbContext { protected BaseDbContext(DbContextOptions options) : base(options) { } // 公共实体集合定义 public DbSet<User> Users { get; set; } public DbSet<Project> Projects { get; set; } }2.2 配置驱动的依赖注入
通过appsettings.json实现环境差异化配置是.NET Core的强项。我们定义专门的数据配置节:
{ "Database": { "Provider": "PostgreSQL", // 或 "SQLite" "ConnectionString": "Host=localhost;Database=hagicode;Username=postgres" } }在Program.cs中动态注册服务:
var dbConfig = builder.Configuration.GetSection("Database").Get<DatabaseConfig>(); builder.Services.AddDbContext<AppDbContext>(options => { if(dbConfig.Provider == "SQLite") { options.UseSqlite(dbConfig.ConnectionString); } else { options.UseNpgsql(dbConfig.ConnectionString, npgsql => { npgsql.UseJsonNet(); npgsql.EnableRetryOnFailure(); }); } });3. 数据库差异处理实战
3.1 JSON类型兼容方案
PostgreSQL的JSONB类型与SQLite的TEXT存储存在显著差异。我们通过EF Core的值转换器实现透明转换:
modelBuilder.Entity<AIPrompt>(entity => { entity.Property(e => e.Metadata) .HasColumnType("jsonb") // PostgreSQL专用 .HasConversion( v => JsonSerializer.Serialize(v, null), v => JsonSerializer.Deserialize<PromptMetadata>(v, null) ); });注意:SQLite会忽略HasColumnType注解,但转换器确保数据能正确序列化存储。实际查询时,复杂JSON操作需考虑SQLite的功能限制。
3.2 迁移策略分离
绝对不要共用迁移脚本!我们采用目录隔离策略:
Migrations/ ├── PostgreSQL/ │ ├── 20240201000000_Initial.cs │ └── 20240202000000_AddJsonSupport.cs └── SQLite/ ├── 20240201000000_Initial.cs └── 20240202000000_AddJsonSupport.cs通过EF Core工具指定输出目录:
# PostgreSQL迁移 dotnet ef migrations add Initial -o Migrations/PostgreSQL # SQLite迁移 dotnet ef migrations add Initial -o Migrations/SQLite4. 性能优化与生产实践
4.1 并发控制机制
PostgreSQL采用多版本并发控制(MVCC),而SQLite使用文件锁。我们针对不同场景实施优化:
// PostgreSQL连接配置 options.UseNpgsql(connStr, o => { o.MaxPoolSize = 100; o.UseNodaTime(); }); // SQLite配置 options.UseSqlite(connStr, o => { o.CommandTimeout(60); // 延长超时时间 o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); });4.2 事务处理差异
PostgreSQL支持完整的事务隔离级别,而SQLite仅支持SERIALIZABLE和READ UNCOMMITTED。我们引入补偿事务模式:
try { using var transaction = await dbContext.Database.BeginTransactionAsync(); // 业务操作 await transaction.CommitAsync(); } catch(DbUpdateException ex) when(dbConfig.Provider == "SQLite") { // SQLite特有的重试逻辑 await Task.Delay(100); await ExecuteWithRetry(async () => { // 重试操作 }, maxRetry: 3); }5. 测试策略与持续集成
5.1 分层测试体系
我们建立了严格的测试金字塔:
- 单元测试:Mock数据库,验证业务逻辑
- 集成测试:针对SQLite快速验证
- 端到端测试:在CI中运行PostgreSQL测试
# GitHub Actions示例 jobs: test: strategy: matrix: db: [sqlite, postgresql] steps: - run: docker-compose up -d db-${{ matrix.db }} - run: dotnet test --filter "Category=${{ matrix.db }}"5.2 差异测试标记
通过TestCategory区分数据库相关测试:
[Fact] [Trait("Category", "PostgreSQL")] public async Task JsonbQuery_ShouldWorkInPostgres() { // PostgreSQL特有功能测试 } [Fact] [Trait("Category", "SQLite")] public async Task TextJsonQuery_ShouldWorkInSqlite() { // SQLite兼容性测试 }6. 部署与运维实践
6.1 容器化部署方案
我们采用Docker Compose实现环境一致性:
services: db: image: ${DB_IMAGE:-postgres:15} environment: POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres} volumes: - pgdata:/var/lib/postgresql/data app: build: . environment: Database__Provider: ${DB_PROVIDER:-PostgreSQL} Database__ConnectionString: ${DB_CONNECTION_STRING} depends_on: - db6.2 监控与调优
针对不同数据库配置监控指标:
// PostgreSQL监控 services.AddNpgsqlDataSourceMetrics(); // SQLite健康检查 services.AddHealthChecks() .AddSqlite("Data Source=hagicode.db");7. 经验总结与避坑指南
在实际项目中我们总结了这些关键经验:
- 连接管理:PostgreSQL需要连接池,SQLite需要及时释放连接
- 类型映射:DateTime处理、Decimal精度等需特别注意
- 索引策略:相同SQL在不同数据库可能需不同索引
- 分页查询:语法差异需统一封装
- 全文搜索:需抽象不同实现(PG的TSearch vs SQLite的FTS)
一个典型的坑是SQLite的ALTER TABLE限制。我们通过迁移预处理解决:
protected override void Up(MigrationBuilder migrationBuilder) { if(ActiveProvider == "Microsoft.EntityFrameworkCore.Sqlite") { // SQLite特有的迁移逻辑 migrationBuilder.Sql("PRAGMA foreign_keys=OFF"); // 重建表实现列修改 } else { // PostgreSQL标准ALTER migrationBuilder.AddColumn<string>(...); } }这套架构已在HagiCode稳定运行半年,支持了20+开发者的协作,日均处理10万+请求。关键收获是:前期适当的抽象投入,能大幅降低后期维护成本。当我们需要新增MySQL支持时,只需添加新的DbContext配置即可。