# 数据库模块检查与优化设计文档 ## 概述 本文档分析 Koa3 项目的数据库模块存在的问题,并提供优化方案。通过深入分析代码结构、模型设计、查询缓存和错误处理机制,识别潜在问题并提出改进建议。 ## 技术栈分析 - **数据库**: SQLite3 - **ORM框架**: Knex.js - **缓存机制**: 内存缓存(自定义实现) - **项目类型**: 后端应用(Node.js + Koa3) ## 架构分析 ### 当前架构结构 ```mermaid graph TB A[应用层] --> B[模型层] B --> C[数据库连接层] C --> D[SQLite数据库] B --> E[查询缓存层] E --> F[内存缓存] C --> G[Knex QueryBuilder] G --> H[迁移系统] G --> I[种子数据] ``` ### 数据模型关系图 ```mermaid erDiagram Users { int id PK string username string email UK string password string role string phone int age string name text bio string avatar string status timestamp created_at timestamp updated_at } Articles { int id PK string title text content string author string category string tags string keywords string description string status timestamp published_at int view_count string featured_image text excerpt int reading_time string meta_title text meta_description string slug UK timestamp created_at timestamp updated_at } Bookmarks { int id PK int user_id FK string title string url text description timestamp created_at timestamp updated_at } SiteConfig { int id PK string key UK text value timestamp created_at timestamp updated_at } Contacts { int id PK string name string email string subject text message string ip_address text user_agent string status timestamp created_at timestamp updated_at } Users ||--o{ Bookmarks : "拥有" ``` ## 问题识别与分析 ### 1. 数据库连接问题 #### 问题描述 - 连接池配置不合理,SQLite设置为最大1个连接,在高并发场景下可能成为瓶颈 - 缺少连接重试机制和错误恢复策略 - 没有健康检查机制 #### 影响评估 - **性能影响**: 高并发场景下连接竞争导致性能下降 - **稳定性风险**: 连接异常时缺少恢复机制 ### 2. 模型设计问题 #### 问题描述 - 模型方法返回值不一致,部分返回数组,部分返回对象 - 缺少统一的错误处理机制 - 模型之间缺少关联查询方法 - 批量操作支持不足 #### 影响评估 - **开发效率**: 不一致的API增加开发复杂度 - **维护成本**: 缺少统一规范导致维护困难 ### 3. 查询缓存问题 #### 问题描述 - 缓存键生成策略不合理,可能产生冲突 - 缺少缓存失效策略和一致性保证 - 没有缓存命中率监控 #### 影响评估 - **数据一致性**: 缓存与数据库数据不同步 - **内存泄漏**: 缓存无限增长可能导致内存问题 ### 4. 事务处理问题 #### 问题描述 - 模型方法缺少事务支持 - 没有原子操作保证 - 复杂业务逻辑缺少事务包装 #### 影响评估 - **数据完整性**: 并发操作可能导致数据不一致 - **业务逻辑**: 复杂操作缺少原子性保证 ### 5. 索引优化问题 #### 问题描述 - 部分查询缺少合适的索引 - 复合索引设计不合理 - 缺少查询性能监控 #### 影响评估 - **查询性能**: 缺少索引导致查询缓慢 - **扩展性**: 数据量增长时性能急剧下降 ## 优化方案设计 ### 1. 数据库连接优化 #### 连接池配置改进 ```javascript // knexfile.mjs 优化配置 export default { development: { client: "sqlite3", connection: { filename: "./database/development.sqlite3", }, pool: { min: 1, max: 3, // 适当增加连接数 acquireTimeoutMillis: 60000, createTimeoutMillis: 30000, destroyTimeoutMillis: 5000, idleTimeoutMillis: 30000, reapIntervalMillis: 1000, createRetryIntervalMillis: 200, afterCreate: (conn, done) => { conn.run("PRAGMA journal_mode = WAL", done) conn.run("PRAGMA synchronous = NORMAL", done) conn.run("PRAGMA cache_size = 1000", done) conn.run("PRAGMA temp_store = MEMORY", done) }, }, } } ``` #### 健康检查机制 ```javascript // db/index.js 添加健康检查 export const checkHealth = async () => { try { await db.raw("SELECT 1") return { status: "healthy", timestamp: new Date() } } catch (error) { return { status: "unhealthy", error: error.message, timestamp: new Date() } } } ``` ### 2. 模型设计优化 #### 统一基础模型类 ```javascript // db/models/BaseModel.js class BaseModel { static get tableName() { throw new Error("tableName must be defined") } static async findById(id) { const result = await db(this.tableName).where("id", id).first() return result || null } static async findAll(options = {}) { const { page = 1, limit = 10, orderBy = "id", order = "desc" } = options const offset = (page - 1) * limit return db(this.tableName) .orderBy(orderBy, order) .limit(limit) .offset(offset) } static async create(data) { const [result] = await db(this.tableName) .insert({ ...data, created_at: db.fn.now(), updated_at: db.fn.now(), }) .returning("*") return result } static async update(id, data) { const [result] = await db(this.tableName) .where("id", id) .update({ ...data, updated_at: db.fn.now(), }) .returning("*") return result } static async delete(id) { return db(this.tableName).where("id", id).del() } static async count(conditions = {}) { const result = await db(this.tableName).where(conditions).count("id as count").first() return parseInt(result.count) } } ``` #### 关联查询方法 ```javascript // 扩展模型关联查询 class ArticleModel extends BaseModel { static get tableName() { return "articles" } // 获取作者相关文章 static async findByAuthorWithProfile(author) { return db(this.tableName) .select("articles.*", "users.name as author_name", "users.avatar as author_avatar") .leftJoin("users", "articles.author", "users.username") .where("articles.author", author) .where("articles.status", "published") } } class BookmarkModel extends BaseModel { static get tableName() { return "bookmarks" } // 获取用户书签(包含用户信息) static async findByUserWithProfile(userId) { return db(this.tableName) .select("bookmarks.*", "users.username", "users.name") .leftJoin("users", "bookmarks.user_id", "users.id") .where("bookmarks.user_id", userId) } } ``` ### 3. 查询缓存优化 #### 改进缓存键生成策略 ```javascript // db/index.js 缓存优化 const getCacheKeyForBuilder = (builder) => { if (builder._customCacheKey) return String(builder._customCacheKey) // 改进键生成策略 const sql = builder.toString() const tableName = builder._single.table || 'unknown' const hash = require('crypto').createHash('md5').update(sql).digest('hex') return `${tableName}:${hash}` } // 添加缓存统计 export const getCacheStats = () => { let valid = 0 let expired = 0 let totalSize = 0 for (const [key, entry] of queryCache.entries()) { if (isExpired(entry)) { expired++ } else { valid++ totalSize += JSON.stringify(entry.value).length } } return { totalKeys: queryCache.size, validKeys: valid, expiredKeys: expired, totalSize, hitRate: valid / (valid + expired) || 0 } } ``` #### 缓存一致性策略 ```javascript // 数据变更时自动清理相关缓存 buildKnex.QueryBuilder.extend("invalidateCache", function() { const tableName = this._single.table if (tableName) { DbQueryCache.clearByPrefix(`${tableName}:`) } return this }) // 在模型的 CUD 操作后自动清理缓存 class BaseModel { static async create(data) { const result = await db(this.tableName).insert(data).returning("*") await db(this.tableName).invalidateCache() return result[0] } static async update(id, data) { const result = await db(this.tableName) .where("id", id) .update(data) .returning("*") await db(this.tableName).invalidateCache() return result[0] } static async delete(id) { const result = await db(this.tableName).where("id", id).del() await db(this.tableName).invalidateCache() return result } } ``` ### 4. 事务处理优化 #### 事务工具函数 ```javascript // db/transaction.js export const withTransaction = async (callback) => { const trx = await db.transaction() try { const result = await callback(trx) await trx.commit() return result } catch (error) { await trx.rollback() throw error } } // 使用示例 export const createUserWithProfile = async (userData, profileData) => { return withTransaction(async (trx) => { const [user] = await trx("users").insert(userData).returning("*") const [profile] = await trx("user_profiles") .insert({ ...profileData, user_id: user.id }) .returning("*") return { user, profile } }) } ``` #### 批量操作优化 ```javascript // 批量插入优化 class BaseModel { static async createMany(dataArray, batchSize = 100) { const results = [] for (let i = 0; i < dataArray.length; i += batchSize) { const batch = dataArray.slice(i, i + batchSize) const batchResults = await db(this.tableName) .insert(batch) .returning("*") results.push(...batchResults) } await db(this.tableName).invalidateCache() return results } static async updateMany(conditions, data) { const result = await db(this.tableName) .where(conditions) .update({ ...data, updated_at: db.fn.now() }) await db(this.tableName).invalidateCache() return result } } ``` ### 5. 索引优化建议 #### 添加必要索引 ```javascript // 新增迁移文件:add_performance_indexes.mjs export const up = async (knex) => { // 用户表索引 await knex.schema.alterTable("users", (table) => { table.index(["email"]) table.index(["username"]) table.index(["status", "created_at"]) }) // 文章表索引 await knex.schema.alterTable("articles", (table) => { table.index(["author", "status"]) table.index(["category", "published_at"]) table.index(["status", "view_count"]) table.index(["tags"]) // 用于标签搜索 }) // 书签表索引 await knex.schema.alterTable("bookmarks", (table) => { table.index(["user_id", "created_at"]) table.index(["url"]) // 用于URL查重 }) // 联系人表索引 await knex.schema.alterTable("contacts", (table) => { table.index(["email", "created_at"]) table.index(["status", "created_at"]) }) } ``` ### 6. 错误处理优化 #### 统一错误处理机制 ```javascript // db/errors.js export class DatabaseError extends Error { constructor(message, code, originalError) { super(message) this.name = "DatabaseError" this.code = code this.originalError = originalError } } export const handleDatabaseError = (error) => { if (error.code === "SQLITE_CONSTRAINT") { return new DatabaseError("数据约束违反", "CONSTRAINT_VIOLATION", error) } if (error.code === "SQLITE_BUSY") { return new DatabaseError("数据库忙,请稍后重试", "DATABASE_BUSY", error) } return new DatabaseError("数据库操作失败", "DATABASE_ERROR", error) } // 在模型中使用 class BaseModel { static async findById(id) { try { return await db(this.tableName).where("id", id).first() } catch (error) { throw handleDatabaseError(error) } } } ``` ### 7. 性能监控优化 #### 查询性能监控 ```javascript // db/monitor.js const queryStats = new Map() export const logQuery = (sql, duration) => { const key = sql.split(' ')[0].toUpperCase() // SELECT, INSERT, UPDATE, DELETE if (!queryStats.has(key)) { queryStats.set(key, { count: 0, totalTime: 0, avgTime: 0 }) } const stats = queryStats.get(key) stats.count++ stats.totalTime += duration stats.avgTime = stats.totalTime / stats.count } export const getQueryStats = () => { return Object.fromEntries(queryStats) } // 在 knex 配置中添加查询日志 export default { development: { // ... 其他配置 log: { warn(message) { console.warn(message) }, error(message) { console.error(message) }, deprecate(message) { console.log(message) }, debug(message) { if (message.sql) { const duration = message.bindings ? message.duration : 0 logQuery(message.sql, duration) } }, }, } } ``` ## 测试策略 ### 单元测试框架 ```javascript // tests/models/BaseModel.test.js import { expect } from 'chai' import { BaseModel } from '../src/db/models/BaseModel.js' describe('BaseModel', () => { it('应该正确创建记录', async () => { const data = { name: 'test' } const result = await TestModel.create(data) expect(result).to.have.property('id') expect(result.name).to.equal('test') }) it('应该正确处理事务', async () => { await expect( withTransaction(async (trx) => { await trx('test_table').insert({ name: 'test' }) throw new Error('回滚测试') }) ).to.be.rejected const count = await TestModel.count() expect(count).to.equal(0) }) }) ``` ### 性能测试 ```javascript // tests/performance/cache.test.js describe('缓存性能测试', () => { it('缓存命中率应该大于80%', async () => { // 执行大量查询 for (let i = 0; i < 1000; i++) { await ArticleModel.findById(1).cache(60000) } const stats = getCacheStats() expect(stats.hitRate).to.be.greaterThan(0.8) }) }) ``` ## 迁移计划 ### 阶段1: 基础优化(1-2周) 1. 修复数据库连接配置 2. 统一模型返回值格式 3. 添加基础错误处理 ### 阶段2: 功能增强(2-3周) 1. 实现统一基础模型类 2. 添加关联查询方法 3. 优化查询缓存机制 ### 阶段3: 性能优化(1-2周) 1. 添加必要索引 2. 实现事务支持 3. 添加性能监控 ### 阶段4: 测试与验证(1周) 1. 编写单元测试 2. 性能基准测试 3. 生产环境验证