Files
cps-develop-docs/02 - Design Standard/2.3 数据库与存储设计规范.md
2026-03-24 16:13:32 +08:00

8.1 KiB
Raw Blame History

2.3 数据库与存储设计规范

数据库与缓存是系统最核心的底座。代码可以随时重构,但糟糕的数据结构一旦上线并积累了大量数据,重构成本将是毁灭性的。本规范旨在确保数据结构的可读性、扩展性及高并发下的性能稳定

注:本章节主要以关系型数据库(如 MySQL/PostgreSQL及 Redis 为基准,其他 NoSQL 存储可参照核心思想灵活应用。

2.3.1 表结构与字段设计规范

0. 框架原生范式顺应原则 (极其重要)

许多现代 Web 框架及其绑定的 ORM如 Django ORM, Ruby on Rails ActiveRecord, Spring Data JPA 等)拥有自己强烈推崇的数据库设计范式(例如:特定的表名生成规则、默认的审计字段命名 created_at、隐含的关联表主外键逻辑

  • 规约: 在此重申“避免对抗框架”的原则。如果项目高度依赖此类重型全栈框架,请优先遵守框架的原生数据库约定,以最大化利用框架的内置生态和开发效率;但对于使用 MyBatis, GORM, SQLAlchemy 等轻量级或自定义程度较高的 ORM 项目,则必须严格遵守下述通用的结构与命名规范。

1. 命名规范(通用红线)

  • 表名与字段名: 必须全部使用小写字母,单词之间使用下划线 _ 分隔Snake Case。严禁使用驼峰命名或拼音。
  • 表名要求: 必须是名词建议增加业务模块前缀。例如sys_user系统模块-用户、ord_order订单模块-订单)。
  • 禁用保留字: 严禁使用数据库保留字(如 desc, order, match, key 等)作为表名或字段名。

2. 字段设计与类型选择

  • 主键设计: * 默认推荐使用 BIGINT 配合雪花算法 (Snowflake ID) 或单调递增的分布式 ID 作为主键,有利于分库分表。
    • 若为中小型单体项目,允许使用数据库自增 AUTO_INCREMENT。
    • 强烈不建议使用无序的 UUID 字符串作为主键(会导致 B+ 树频繁页分裂,严重影响写入性能)。
  • 金额类型: 涉及财务、金额的字段,必须使用 DECIMAL如 DECIMAL(10,2))或将元转为分存储为 BIGINT。绝对禁止使用 FLOAT 或 DOUBLE会产生精度丢失
  • 布尔与枚举值: 建议使用 TINYINT(1) 或 TINYINT(4)。字段命名建议以 is_ 或 has_ 开头(如 is_deletedhas_attachments并在注释中明确写清枚举含义如 0:否, 1:是)。
  • 字符串类型: 定长使用 CHAR变长使用 VARCHAR。尽量避免使用 TEXT 或 BLOB如必须使用建议将此类大字段垂直拆分到附属表中,以免拖慢主表的查询效率。
  • 字符集: 默认强制使用 utf8mb4以完美支持 Emoji 表情和生僻字;排序规则推荐使用 utf8mb4_general_ci。

3. 必备的审计字段

所有核心业务表,强烈建议包含以下四个基础审计字段(注:若所用框架有内置约定的字段名如 created_at / updated_at则以框架约定为准

  • id: 主键。
  • create_time: 创建时间(推荐 DATETIME 或 TIMESTAMP默认值 CURRENT_TIMESTAMP
  • update_time: 更新时间(默认值 CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
  • is_deleted: 逻辑删除标记0正常1删除注意使用逻辑删除时需仔细评估与唯一索引Unique Key的冲突问题。

2.3.2 索引建立原则与慢查询规避

慢查询是导致数据库 CPU 飙升、系统雪崩的头号元凶。开发人员在编写 SQL 时必须脑中有 B+ 树的结构。

1. 索引建立原则 [推荐规范]

  • 控制索引数量: 单表索引数量建议不超过 5 个。索引不是越多越好,过多的索引会严重拖慢 INSERT/UPDATE 的性能。
  • 高区分度优先: 尽量在区分度高的列上建立索引(如 user_idorder_no不要在性别、状态这类只有少数几个值的列上单独建立索引。
  • 拥抱联合索引(最左前缀法则): * 频繁一起查询的多个字段,应建立联合索引,而非多个单列索引。
    • 联合索引的设计必须遵循最左前缀法则。例如索引为 (a, b, c),那么 WHERE a=?、WHERE a=? AND b=? 会走索引,但 WHERE b=? AND c=? 绝对不会走该联合索引。
  • 覆盖索引优先: 尽量让查询的列直接包含在联合索引中,避免“回表”操作,极大提升查询效率。

2. 慢查询规避红线(必须遵守)

  • 禁止 SELECT * 只查询业务需要的字段。SELECT * 会导致无法使用覆盖索引,并增加网络传输负担。
  • 禁止左模糊查询: LIKE '%keyword' 或 LIKE '%keyword%' 会导致索引全表扫描。如需全文检索,请引入 ElasticSearch。右模糊 LIKE 'keyword%' 是允许的(可走索引范围查询)。
  • 禁止在索引列上做计算或函数操作: 例如 WHERE YEAR(create_time) = 2023 会导致索引失效。应改写为 WHERE create_time >= '2023-01-01' AND create_time < '2024-01-01'。
  • 隐式类型转换会导致索引失效: 如果字段类型是 VARCHAR但查询时写成了 WHERE phone = 13800138000数字类型MySQL 会隐式转换导致全表扫描。必须加引号WHERE phone = '13800138000'。
  • 警惕深分页问题: LIMIT 100000, 20 会让数据库扫描 100020 行数据然后丢弃前十万行。
    • 解决建议: 限制最大允许跳页数,或者结合 2.2 章节推荐的“游标分页Cursor”机制如 WHERE id > last_max_id LIMIT 20

2.3.3 Redis 缓存设计规范

Redis 虽然快,但不当的使用会导致内存泄漏和严重的并发故障。

1. 键值 (Key) 命名规范

为了防止不同业务线的 Key 产生冲突,且便于在可视化工具(如 RDM中分类查看Key 必须遵循层级命名法则

  • 格式: 项目名:模块名:实体名:唯一标识
  • 示例: * 用户信息缓存cps:user:info:10086
    • 订单防重放锁cps:order:lock:REQ_99823
  • 规范: 全部小写或大写(团队统一即可,推荐小写),严禁包含空格、换行或特殊字符,长度尽量控制在 64 字节以内以节省内存。

2. Value 设计规范

  • 控制大 KeyBigKey 单个 String 类型的 Value 不应超过 10KBHash、List、Set 元素个数不应超过 5000 个。大 Key 在删除或传输时会阻塞 Redis 单线程模型。
  • 选择合适的数据结构: * 简单的全量缓存用 String (配合 JSON 序列化)。
    • 如果只需要频繁更新或读取对象的某几个字段,应使用 Hash避免频繁的完整 JSON 序列化和反序列化开销。

3. 缓存生命周期与高可用防范(极其重要)

  • 强制设置过期时间 (TTL) 绝不允许向 Redis 写入永久不过期的业务缓存(配置类或基准字典除外)。否则 Redis 内存迟早被撑爆。
  • 防范缓存雪崩 (Cache Avalanche) * 问题: 大量缓存在同一时刻集体过期,导致巨量请求瞬间全部打向数据库,击穿 DB。
    • 对策: 在设置 TTL 时,必须加上一个随机的抖动时间(例如,基础过期时间 2 小时 + 随机 0~300 秒)。
  • 防范缓存穿透 (Cache Penetration) * 问题: 黑客恶意查询一个数据库中绝对不存在的值(如 id=-1导致缓存永远不命中所有请求全打到 DB。
    • 对策: 即使数据库返回 Null也要把这个 Null 值缓存进 Redis设置一个较短的 TTL如 5 分钟);或者引入布隆过滤器 (Bloom Filter) 提前拦截。
  • 防范缓存击穿 (Cache Breakdown) * 问题: 某一个“极其热点”的 Key 突然过期的一瞬间,上万个并发请求发现缓存未命中,同时去查询 DB 并尝试重写缓存,导致 DB 崩溃。
    • 对策: 使用分布式锁(如 Redisson控制只让一个线程去查询 DB 并重建缓存,其他线程等待;或针对极度热点的数据设置“逻辑过期”而非物理过期。