SQL 数据库建模与设计

1次阅读

多数业务场景下优先选 bigint auto_increment 作主键,因其写入高效、索引稳定、利于分库分表;uuid 仅适用于跨系统合并等特定场景,且需用 uuid_to_bin(…, true) 优化。

SQL 数据库建模与设计

主键选 UUID 还是 BIGINT AUTO_INCREMENT

多数业务场景下,BIGINT AUTO_INCREMENT 更稳,尤其当表要高频写入、关联查询多、或需要分库分表时。UUID 看似“全局唯一”很香,但实际会让索引碎片化严重、插入变慢、JOIN 效率下降——因为它是无序的随机字符串

常见错误现象:INSERT 吞吐量突然掉 30%+,EXPLAIN 显示 type=ALLkey_len 异常小,说明索引没被有效利用。

  • 电商订单、用户行为日志这类高写入表,优先用 BIGINTUUID 只在跨系统合并、离线导入、或明确要求客户端生成 ID 的场景才考虑
  • UUID 如果非用不可,用 UUID_TO_BIN(uuid(), TRUE)mysql 8.0+)转成二进制并启用 ORDERED 模式,避免默认字符串排序导致的 B+ 树分裂
  • 别把 UUID 当主键还加 created_at 做联合索引——顺序混乱的前导列会让整个索引失效

TEXT 字段能不能加索引?怎么加才不翻车?

能加,但不能直接对完整 TEXT 加普通索引;MySQL 要求前缀长度必须显式指定,且受存储引擎限制(InnoDB 最大前缀 767 字节,utf8mb4 下最多 191 个字符)。

使用场景:搜索文章标题、商品短描述、错误日志摘要这类「关键前缀有区分度」的字段。

  • 建索引时必须写明长度,例如 INDEX idx_content (content(191));漏写会报错 Error 1170 (42000): BLOB/TEXT column 'content' used in key specification without a key Length
  • 别对长文本全文检索依赖前缀索引——它只加速“开头匹配”,WHERE content LIKE '%关键词%' 完全用不上
  • 真要模糊搜全文,用 FULLTEXT 索引 + MATCH ... AGAINST,但注意它不支持中文分词(需插件或换 elasticsearch

一对多关系,外键到底要不要加 ON delete CASCADE

除非业务逻辑明确要求“删父必删子”,否则别开 ON DELETE CASCADE。它看着省事,实则掩盖数据一致性风险,且容易引发意外的连锁删除和锁等待。

常见错误现象:删一条用户记录,结果连带删了 5 万条订单 + 关联日志,事务卡住 3 秒,监控报警炸锅。

  • 级联删除无法审计——你查不到谁触发的,也难回滚;生产环境应由应用层显式控制删除顺序和范围
  • 如果真要用,务必确保子表有对应外键索引(如 order(user_id)),否则 CASCADE 会全表扫描子表,性能雪崩
  • 更稳妥的做法是设 ON DELETE restrict(默认),靠应用代码先查后删,或用软删除(is_deleted 字段)替代物理删除

时间字段用 DATETIME 还是 timestamp

统一用 DATETIME。它不自动时区转换、范围更大(1000–9999 年)、精度可控(MySQL 5.6.4+ 支持微秒),而 TIMESTAMP 会隐式转时区、2038 年就溢出,且默认值和更新行为容易踩坑。

典型陷阱:TIMESTAMP 列设了 default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,结果因服务器时区变更,所有历史记录的 updated_at 全被悄悄改写成当地时间。

  • DATETIME 初始化和更新都靠应用或触发器控制,行为可预测;TIMESTAMP 的自动行为是黑盒,调试困难
  • 跨时区服务(比如海外用户 + 国内运维)必须用 DATETIME 存 UTC 时间,前端按需格式化——别指望数据库帮你“智能转换”
  • 别为了“省空间”选 TIMESTAMP(4 字节 vs DATETIME 的 5–8 字节);磁盘不是瓶颈,逻辑清晰才是

设计表时最容易被忽略的,其实是字段的「语义闭环」:一个 status 字段,有没有配套的枚举注释?created_atupdated_at 的更新逻辑是否在所有写入口统一?这些细节不会报错,但半年后加新功能时,你会花三小时搞懂自己当初为什么那样写。

text=ZqhQzanResources