表结构拖慢查询主要因全表扫描、过度join、范式滥用、字段非原子化、类型选错等;应合理冗余、分区、列存及选对分布键。

怎么一眼看出表结构拖慢了查询
直接看执行计划里有没有 Seq Scan(全表扫描)或大量 Hash Join,尤其当数据量不大(比如百万级)却要跑几秒以上时,大概率是结构设计埋了雷。不是索引没建好,而是表本身让数据库“不得不绕远路”。
- 查用户订单列表要 JOIN 5 张表?说明关联太深,可能该冗余的没冗余
- 更新一个商品名称,触发上百条订单记录更新?说明违反了 3NF,把可变属性塞进了不该放的地方
-
address字段存 “北京市朝阳区建国路8号”,但业务常按“城市”筛选?字段没原子化,索引失效,只能LIKE '%北京市%' - 所有日志、订单、用户挤在一张
all_in_one表里?哪怕加了索引,B+ 树深度暴涨,随机 IO 成倍增加
数值类型选错,悄悄吃掉 30% 性能
用 BIGINT 存用户 ID,但实际最大值才 20 万——看着没事,但每行多占 4 字节,索引体积膨胀,缓存命中率下降;更隐蔽的是,JOIN 时类型不一致会触发隐式转换,INT 和 BIGINT 关联,可能让索引直接失效。
- 主键优先用
INT或SERIAL,除非明确预估超 21 亿 - 金额字段不用
FLOAT,用DECIMAL(12,2)——精度可控,且避免浮点计算误差导致的索引范围失效 - 状态字段(如
status只有 0/1/2)别用TINYINT或ENUM,MySQL 8.0+ 推荐TINYINT UNSIGNED,语义清晰、排序稳定、兼容性好 - 时间字段:需要时区支持用
TIMESTAMP WITH TIME ZONE,否则一律用TIMESTAMP(非DATETIME),它走系统时区、索引效率略高
什么时候该反范式,什么时候死守 3NF
不是“要不要反范式”,而是“哪一列值得冗余”。关键看读写比和一致性容忍度——如果一条记录每天被查 1000 次、只改 1 次,那冗余就是划算买卖;但如果每改 1 次就要同步 10 张表,就该收手。
- 电商商品页:在
product表加category_name,而不是每次查都JOIN category——前提是分类名极少变更 - 用户总消费额:在
user表加total_amount,订单创建/退款时用事务更新它——别用定时任务异步汇总,延迟和不一致风险太高 - 千万别冗余:密码、token、余额、审批状态等高频变动或强一致性字段
- 冗余字段必须和源字段同事务更新,用
UPDATE ... SET total_amount = total_amount + ? WHERE id = ?这类原子操作,别先SELECT再算再UPDATE
分区和存储格式对性能的影响比你想的大
单表过千万行后,WHERE create_time > '2025-01-01' 这种查询,即使有索引,也可能因数据物理分散导致大量随机 IO。此时按时间分区(如 PARTITION BY RANGE (YEAR(create_time)))能让查询直接跳过无关分区,实测快 3 倍不止。
- 大事实表(如订单、日志)优先用列存(
orientation = column),聚合查询快,但点查略慢;小维度表(如用户、地区)用行存更合适 - 分布键(
DISTRIBUTION KEY)选错等于自废武功:选user_id做分布键,但查询常按order_date过滤?节点间数据重分布开销爆炸 - 小表(REPLICATION,避免跨节点 JOIN,用空间换速度——但别滥用,复制太多反而拖慢写入











