加外键后insert变慢因需校验被引用表主键存在性,若无索引将全表扫描;postgresql需手动建索引,跨库分库场景外键不可用;varchar适合短文本,text用于大内容并分离存储。

为什么加外键后 INSERT 变慢了
外键约束本身会触发额外的校验逻辑,不是单纯“加个索引”就完事。每次 INSERT 或 UPDATE 外键列时,数据库必须去被引用表查一遍对应主键是否存在——如果没走索引,就是全表扫描。
- 被引用字段(如
user_id)必须有索引,否则性能雪崩;MySQL 的FOREIGN KEY要求被引用列自动建索引,但 PostgreSQL 不自动建,得手动加CREATE INDEX ON orders(user_id) - 级联操作(
ON DELETE CASCADE)看着省事,但可能意外删掉大量数据,且事务内锁范围更大 - 跨库、分库场景下外键根本不可用,硬加会导致 DDL 失败或被忽略(比如 MySQL 8.0+ 对非本地表禁用外键)
TEXT 和 VARCHAR(65535) 到底选哪个
VARCHAR 存固定长度短文本更高效,TEXT 是为大内容设计的独立存储机制,二者底层处理方式不同,不能只看“都能存字符串”就混用。
-
VARCHAR(N)最大长度受行大小限制(InnoDB 单行约 65535 字节),且 N 超过 255 时,长度编码从 1 字节升到 2 字节;实际存储还占额外 1–2 字节记录长度 -
TEXT类型(包括TINYTEXT/MEDIUMTEXT)内容超出行大小时会自动移出主行,只留指针;但会导致SELECT *多一次 IO,排序/临时表也更容易落磁盘 - 如果字段平均长度 VARCHAR(2000);如果明确要存日志、HTML 片段、JSON 块,且长度波动极大,用
MEDIUMTEXT
时间字段该用 DATETIME 还是 TIMESTAMP
表面都是存时间,但 DATETIME 是“字面值”,TIMESTAMP 是“带时区语义的 Unix 时间戳”,错用会导致查询结果随服务器时区变化而漂移。
-
TIMESTAMP自动转成 UTC 存储、读取时转回当前会话时区;如果应用层已统一处理时区(比如全用 UTC 存),反而多此一举,还受限于1970–2038年范围 -
DATETIME不做任何转换,存啥读啥,适合记录“事件发生的确切本地时间”,比如会议开始时间、订单创建时间(按业务所在地) - MySQL 5.6.5+ 支持
DATETIME默认值用函数(CURRENT_TIMESTAMP),别再为了自动填充硬套TIMESTAMP
一张表到底要不要拆分成「主表 + 扩展表」
不是所有“字段多”的表都该拆。拆表解决的是行太宽导致的 IO 效率和锁竞争问题,但会引入 JOIN 成本和事务一致性风险。
- 当表中存在少数字段(如
profile_json、settings_blob)访问频次极低,且单行总长 > 8KB,导致一页存不了几行时,拆才有意义 - 拆完必须保证主键一致(扩展表用
user_id当主键兼外键),否则JOIN性能崩;同时避免在扩展表上建太多索引,它本质是“冷数据容器” - ORM 框架对双表映射支持参差不齐,比如 Django 的
OneToOneField默认惰性加载,但select_related()写错位置照样 N+1
范式不是 checklist,是权衡:你敢不敢为读写分离放弃第三范式?能不能接受某个字段在多个地方冗余但靠应用层保证一致?这些决定比“是否加外键”更影响系统寿命。










