联合主键导致二级索引体积增大、查询变慢、回表成本升高、最左前缀失效更隐蔽、写放大加剧及缓存污染;应仅在天然稳定小字段组合且覆盖高频查询时使用,否则优先选自增或有序uuid主键。

联合主键让二级索引变大又变慢
MySQL 的 InnoDB 引擎里,二级索引的叶子节点必须存「索引列值 + 主键值」。如果主键是联合主键(比如 (org_id, user_id)),那每个二级索引项就得额外存两个字段的值,而不是一个 id。这意味着:同一份数据,二级索引体积直接翻倍甚至更多;B+ 树层级变深,一次索引查找要多跳几次节点;缓冲池里能缓存的索引页数变少,Buffer Pool 命中率下降。
常见表现是:EXPLAIN 显示 type=ref 或 range,但实际执行时间远超预期,Handler_read_next 和 Handler_read_rnd_next 计数飙升——本质是单次回表成本没变,但索引扫描本身已更重。
最左前缀失效在联合主键下更隐蔽
联合主键本身就是聚簇索引,它决定了整张表数据的物理排序顺序。如果你建了一个二级索引 idx_status_created_at,而主键是 (tenant_id, id),那 InnoDB 在回表时,得拿着 (tenant_id, id) 去聚簇索引里定位行。但若查询只给了 id,没有 tenant_id,就无法利用主键的有序性做快速定位,只能退化为随机 I/O 查找,甚至触发全表扫描式的聚簇索引遍历。
这种失效不体现在 EXPLAIN 的 key 字段里(它可能仍显示用了你的二级索引),而是藏在 rows 估算严重偏低、Extra 出现 Using filesort 或 Using temporary 里。
- 联合主键字段顺序必须和业务高频查询模式对齐,否则二级索引“能用”但“不好用”
- 不要为了“逻辑唯一性”硬凑联合主键,比如
(app_id, uuid)——uuid本身无序,会彻底打散聚簇索引的局部性 - 如果业务上天然存在强租户隔离(如 SaaS 多租户),
tenant_id放主键最左位是合理选择;但若租户维度只是查询条件之一,它更适合放进二级索引里,而非塞进主键
写放大加剧,间接拖垮二级索引效率
联合主键越宽,每次 INSERT/UPDATE/DELETE 操作带来的写放大就越严重。因为每条记录变更,都要同步更新所有二级索引——每个二级索引项都得完整复制一遍联合主键值。当表有 5 个二级索引、主键是 3 个 VARCHAR(32) 字段时,一次插入实际写入的索引数据量可能是原始行的 2~3 倍。
这不仅拉高磁盘 I/O 和 Redo Log 压力,还会导致 Buffer Pool 更快被脏页占满,进而降低二级索引页和数据页的缓存效率。你看到的现象可能是:白天查询还正常,凌晨批量导入后,第二天所有带二级索引的查询都明显变慢——问题不在索引本身,而在联合主键引发的持续性缓存污染。
真正该警惕的不是“能不能建”,而是“要不要建”
联合主键不是技术炫技,它是对数据物理组织方式的强制承诺。一旦选定,所有二级索引都会继承它的代价。很多线上事故不是因为没加索引,而是因为主键设计把二级索引拖进了性能泥潭:索引体积膨胀、回表路径变长、写入抖动放大、统计信息失真……这些影响不会立刻报错,但会在高并发或大数据量下集中爆发。
判断是否该用联合主键,关键看两点:一是业务是否有不可拆分的自然主键组合(且字段足够小、足够稳定);二是这个组合是否能覆盖绝大多数范围查询、排序和连接场景。否则,老老实实用自增 BIGINT 或 UUID(带时间戳前缀)作主键,把业务维度放到二级索引里,反而更可控。











