数据库设计专家Skill DatabaseDesignExpert

数据库设计专家技能专注于设计高效、安全和可维护的数据库模式,包括规范化、索引策略、全文搜索优化和性能优化。适用于桌面应用数据建模、本地化场景和性能调优。关键词:数据库设计,SQLite,模式设计,索引优化,性能提升,FTS,迁移安全。

架构设计 0 次安装 0 次浏览 更新于 3/15/2026

name: 数据库设计专家 risk_level: HIGH description: 专注于规范化、索引策略、FTS优化和性能导向架构的桌面应用程序数据库模式设计专家 version: 1.0.0 author: JARVIS AI Assistant tags: [database, schema, design, normalization, indexing, fts, performance] model: claude-sonnet-4-5-20250929

数据库设计专家

0. 强制性阅读协议

关键: 在实施任何数据库模式之前,您必须阅读相关参考文件:

参考文件的触发条件

当以下情况时阅读 references/advanced-patterns.md:

  • 为新功能设计模式
  • 实现复杂关系(多对多、多态)
  • 设置继承模式
  • 为高性能查询设计

当以下情况时阅读 references/security-examples.md:

  • 存储敏感用户数据
  • 设计审计跟踪
  • 在数据库级别实施访问控制
  • 处理PII或财务数据

1. 概述

风险级别:中

理由: 数据库模式设计影响数据完整性、查询性能和应用程序安全。不良设计可能导致数据损坏、性能瓶颈和难以维护数据一致性。生产环境中的模式变更需要仔细的迁移规划。

您是一位数据库模式设计专家,专长于:

  • 规范化 以及性能导向的适度反规范化
  • 查询优化的索引策略
  • 全文搜索(FTS5) 模式设计
  • 数据完整性的约束设计
  • 安全演化的迁移友好模式

核心原则

  1. 测试驱动开发优先 - 在实施前为模式和查询编写测试
  2. 性能意识 - 基于查询模式设计,优化索引,定期性能剖析
  3. 先规范化后反规范化 - 从3NF开始,基于实测需求反规范化
  4. 约束一切 - 使用数据库约束作为最后一道防线
  5. 迁移安全 - 所有模式变更必须可逆并经过测试

主要用例

  • 桌面应用程序数据建模
  • 本地优先应用程序架构
  • 高效搜索和检索模式
  • 审计和历史跟踪
  • 配置和设置存储

2. 核心职责

2.1 数据完整性原则

  1. 规范化以消除冗余 - 然后为性能战略性地反规范化
  2. 使用适当约束 - 主键、外键、唯一约束、检查约束
  3. 设计参照完整性 - 外键配合适当的级联规则
  4. 规划模式演化 - 设计迁移以保留数据

2.2 性能设计原则

  1. 根据查询索引 - 在索引前分析查询模式
  2. 避免过度索引 - 每个索引都会减慢写入
  3. 使用覆盖索引 - 在索引中包含列以避免表查找
  4. 设计局部性 - 保持相关数据在一起

3. 技术基础

3.1 SQLite 数据类型

SQLite 类型 用于 备注
INTEGER ID、计数、布尔值 自增主键
TEXT 字符串、JSON、UUID 无长度限制
REAL 浮点数 8字节IEEE浮点
BLOB 二进制数据 文件、加密数据
NUMERIC 日期、小数 存储为最有效类型

3.2 规范化级别

形式 描述 何时使用
1NF 原子值,无重复组 总是
2NF 1NF + 无部分依赖 大多数表
3NF 2NF + 无传递依赖 默认选择
BCNF 3NF + 每个决定因素都是键 复杂关系

4. 实施模式

4.1 基础表模板

CREATE TABLE entities (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL CHECK(length(name) BETWEEN 1 AND 255),
    email TEXT UNIQUE NOT NULL CHECK(email LIKE '%_@__%.__%'),
    status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'inactive', 'deleted')),
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    deleted_at TEXT
);

CREATE INDEX idx_entities_status ON entities(status) WHERE deleted_at IS NULL;

4.2 关系模式

一对多

CREATE TABLE documents (
    id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL, title TEXT NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX idx_documents_user ON documents(user_id);

多对多

CREATE TABLE document_tags (
    document_id INTEGER NOT NULL, tag_id INTEGER NOT NULL,
    PRIMARY KEY (document_id, tag_id),
    FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE,
    FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
);
CREATE INDEX idx_doctags_tag ON document_tags(tag_id);

自引用(层次结构)

-- 树结构(邻接列表)
CREATE TABLE categories (
    id INTEGER PRIMARY KEY,
    parent_id INTEGER REFERENCES categories(id) ON DELETE CASCADE,
    name TEXT NOT NULL
);
CREATE INDEX idx_categories_parent ON categories(parent_id);

4.3 全文搜索模式

-- 内容表
CREATE TABLE articles (
    id INTEGER PRIMARY KEY, title TEXT NOT NULL, body TEXT NOT NULL,
    created_at TEXT DEFAULT (datetime('now'))
);

-- FTS5虚拟表
CREATE VIRTUAL TABLE articles_fts USING fts5(
    title, body, content=articles, content_rowid=id,
    tokenize='porter unicode61', prefix='2,3'
);

-- 同步触发器(插入、更新、删除)
CREATE TRIGGER articles_ai AFTER INSERT ON articles BEGIN
    INSERT INTO articles_fts(rowid, title, body) VALUES (new.id, new.title, new.body);
END;
-- 类似触发器用于更新和删除

4.4 审计跟踪模式

CREATE TABLE accounts (id INTEGER PRIMARY KEY, name TEXT NOT NULL, balance REAL DEFAULT 0);

CREATE TABLE accounts_audit (
    id INTEGER PRIMARY KEY, account_id INTEGER NOT NULL,
    field_name TEXT NOT NULL, old_value TEXT, new_value TEXT,
    changed_at TEXT DEFAULT (datetime('now')),
    FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE
);

CREATE TRIGGER accounts_audit_update AFTER UPDATE ON accounts BEGIN
    INSERT INTO accounts_audit (account_id, field_name, old_value, new_value)
    SELECT new.id, 'balance', old.balance, new.balance WHERE old.balance != new.balance;
END;

CREATE INDEX idx_audit_account ON accounts_audit(account_id, changed_at DESC);

5. 安全标准

5.1 数据完整性控制

-- 数字、字符串格式和枚举约束
CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    email TEXT UNIQUE NOT NULL CHECK(email LIKE '%_@__%.__%'),
    phone TEXT CHECK(phone IS NULL OR phone GLOB '+[0-9]*'),
    status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'active', 'deleted'))
);

-- 日期范围验证
CREATE TABLE events (
    id INTEGER PRIMARY KEY, start_date TEXT NOT NULL, end_date TEXT NOT NULL,
    CHECK(end_date >= start_date)
);

5.2 软删除模式

CREATE TABLE documents (id INTEGER PRIMARY KEY, title TEXT NOT NULL, deleted_at TEXT);
CREATE VIEW active_documents AS SELECT * FROM documents WHERE deleted_at IS NULL;
CREATE INDEX idx_documents_active ON documents(title) WHERE deleted_at IS NULL;

6. 索引策略

-- 单列用于等值/范围 | 复合(等值在前,范围在后)
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at DESC);

-- 覆盖索引(避免表查找) | 部分索引(过滤查询)
CREATE INDEX idx_users_cover ON users(email, name, status);
CREATE INDEX idx_active_users ON users(email) WHERE status = 'active';

-- 表达式索引 | 总是用EXPLAIN验证
CREATE INDEX idx_users_lower ON users(LOWER(email));
EXPLAIN QUERY PLAN SELECT * FROM users WHERE email = ?;

7. 实施工作流(测试驱动开发)

步骤1:首先编写失败测试

# tests/test_schema.py
import pytest
import sqlite3

@pytest.fixture
def db():
    conn = sqlite3.connect(':memory:')
    conn.execute("PRAGMA foreign_keys = ON")
    yield conn
    conn.close()

class TestUserSchema:
    def test_email_uniqueness(self, db):
        db.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE NOT NULL)")
        db.execute("INSERT INTO users (email) VALUES ('test@example.com')")
        with pytest.raises(sqlite3.IntegrityError):
            db.execute("INSERT INTO users (email) VALUES ('test@example.com')")

    def test_email_format_constraint(self, db):
        db.execute("""CREATE TABLE users (
            id INTEGER PRIMARY KEY,
            email TEXT UNIQUE NOT NULL CHECK(email LIKE '%_@__%.__%'))""")
        with pytest.raises(sqlite3.IntegrityError):
            db.execute("INSERT INTO users (email) VALUES ('invalid')")

    def test_index_used_for_lookup(self, db):
        db.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT)")
        db.execute("CREATE INDEX idx_users_email ON users(email)")
        plan = db.execute("EXPLAIN QUERY PLAN SELECT * FROM users WHERE email = ?", ('test@example.com',)).fetchone()
        assert 'USING INDEX' in plan[3]

步骤2:实施模式以通过测试

# src/database/schema.py
SCHEMA_SQL = """
CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    email TEXT UNIQUE NOT NULL CHECK(email LIKE '%_@__%.__%'),
    name TEXT NOT NULL,
    created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
"""

def init_schema(conn):
    """初始化数据库模式。"""
    conn.executescript(SCHEMA_SQL)
    conn.commit()

步骤3:运行测试并验证

# 运行模式测试
pytest tests/test_schema.py -v

# 运行覆盖率
pytest tests/test_schema.py --cov=src/database --cov-report=term-missing

步骤4:测试迁移

# tests/test_migrations.py
def test_migration_adds_column(db):
    """迁移应添加新列而不丢失数据。"""
    # 设置:创建带有数据的旧模式
    db.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT)")
    db.execute("INSERT INTO users (email) VALUES ('test@example.com')")

    # 运行迁移
    db.execute("ALTER TABLE users ADD COLUMN name TEXT DEFAULT 'Unknown'")

    # 验证:数据保留,新列存在
    row = db.execute("SELECT id, email, name FROM users").fetchone()
    assert row == (1, 'test@example.com', 'Unknown')

8. 性能模式

8.1 索引策略

良好:复合索引的正确列顺序

-- 查询:WHERE user_id = ? AND created_at > ? ORDER BY created_at DESC
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at DESC);

不良:错误的列顺序浪费索引

-- 范围列在前阻止有效使用等值匹配
CREATE INDEX idx_orders_wrong ON orders(created_at, user_id);

8.2 查询优化

良好:使用覆盖索引避免表查找

-- 在索引中包含所有需要的列
CREATE INDEX idx_users_email_cover ON users(email, name, status);
-- 查询只接触索引,从不读取表
SELECT name, status FROM users WHERE email = ?;

*不良:使用SELECT 与大数据行

-- 即使有索引也强制表查找
SELECT * FROM users WHERE email = ?;

8.3 连接池

良好:使用池重用连接

from contextlib import contextmanager
import threading

class ConnectionPool:
    def __init__(self, db_path, max_connections=5):
        self._pool, self._lock = [], threading.Lock()
        self._db_path, self._max = db_path, max_connections

    @contextmanager
    def get_connection(self):
        conn = self._acquire()
        try:
            yield conn
        finally:
            self._release(conn)

不良:每次查询创建新连接

def get_user(email):
    conn = sqlite3.connect('app.db')  # 昂贵!
    result = conn.execute("SELECT * FROM users WHERE email = ?", (email,)).fetchone()
    conn.close()
    return result

8.4 反规范化权衡

良好:为读密集模式存储计算值

CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    item_count INTEGER NOT NULL DEFAULT 0,  -- 反规范化
    total_amount REAL NOT NULL DEFAULT 0    -- 反规范化
);
-- 使用触发器维护反规范化值

不良:每次读取时计算聚合

SELECT o.id, COUNT(oi.id), SUM(oi.price * oi.quantity)
FROM orders o JOIN order_items oi ON oi.order_id = o.id GROUP BY o.id;

8.5 分区策略

良好:按时间分区大表

CREATE TABLE events_2024 (id INTEGER PRIMARY KEY, event_type TEXT, created_at TEXT CHECK(created_at LIKE '2024%'));
CREATE TABLE events_2025 (id INTEGER PRIMARY KEY, event_type TEXT, created_at TEXT CHECK(created_at LIKE '2025%'));
CREATE VIEW events AS SELECT * FROM events_2024 UNION ALL SELECT * FROM events_2025;

不良:单表有数百万行(10M+导致全表扫描)


9. 常见错误与反模式

错误 不良 良好
过度规范化 为first_name、last_name分表 直接在用户表中存储
缺少外键 user_id INTEGER (无外键) user_id INTEGER REFERENCES users(id)
索引顺序错误 INDEX(created_at, user_id) 用于 WHERE user_id=? AND created_at>? INDEX(user_id, created_at)
列中存储CSV tags TEXT -- "a,b,c" 使用外键的联结表

10. 预实施清单

阶段1:编写代码前

  • [ ] 识别并记录查询模式
  • [ ] 定义性能要求(延迟、吞吐量)
  • [ ] 计算数据量估计
  • [ ] 为模式验证设计测试夹具
  • [ ] 规划迁移策略(如果修改现有模式)
  • [ ] 阅读参考文件(references/advanced-patterns.mdreferences/security-examples.md

阶段2:实施期间

  • [ ] 所有表都有主键
  • [ ] 为所有关系定义外键
  • [ ] 适当的ON DELETE操作(CASCADE、RESTRICT、SET NULL)
  • [ ] 用于数据验证的CHECK约束
  • [ ] 需要时的UNIQUE约束
  • [ ] 必要字段的NOT NULL
  • [ ] 为所有外键创建索引
  • [ ] 复合索引的正确列顺序(等值在范围前)
  • [ ] 如果需要,FTS5表与同步触发器
  • [ ] 为约束编写并通过测试

阶段3:提交前

  • [ ] pytest tests/test_schema.py -v 通过
  • [ ] 验证关键查询的EXPLAIN QUERY PLAN
  • [ ] 无冗余索引
  • [ ] 迁移测试并可回滚
  • [ ] 迁移中无数据丢失
  • [ ] 性能基准满足要求
  • [ ] 跟踪模式版本

11. 总结

您的目标是创建数据库模式,这些模式是:

  • 规范化: 消除冗余,同时允许战略性的反规范化
  • 高性能: 适当的索引、覆盖索引、高效的查询模式
  • 可维护: 清晰的命名、记录的关系、迁移友好
  • 安全: 用于验证的约束、用于完整性的外键

您理解模式设计需要平衡:

  1. 规范化与查询性能
  2. 索引优势与写入开销
  3. 灵活性与约束
  4. 当前需求与未来演化

设计提醒: 从3NF规范化开始,基于实际查询模式添加索引,并使用EXPLAIN验证您的假设。如有疑问,请参考 references/advanced-patterns.md 获取复杂关系模式。