联合主键字段顺序必须按查询过滤强度排序:高基数等值字段(如user_id)置最左,低基数或范围字段(如status、created_at)靠右;字段数宜控制在2–3个以内,禁用大字段,避免函数/隐式转换导致索引失效。

联合主键字段顺序必须严格按查询过滤强度排序
联合主键本质是聚簇索引,其B+树只按第一个字段全局排序,后续字段仅在前序值相同时局部有序。如果高频查询是 WHERE user_id = ? AND created_at > ?,但主键定义为 (created_at, user_id),那 user_id 就完全无法利用最左前缀——树里根本没按它建序。
常见错误现象:EXPLAIN 显示 type: ALL 或 key: NULL,明明建了联合主键却全表扫描。
- 高基数、等值查询多的字段(如
user_id、order_id)放最左 - 低基数字段(如
status只有 3–5 个值)尽量别放最左,除非它总是和等值条件一起出现且筛选率高 - 时间字段(
created_at)慎放最左——范围查询会截断右侧所有字段的索引能力
联合主键字段数控制在2–3个以内
每多一个字段,主键值就更大,所有二级索引的叶子节点都要冗余存储这个完整主键,直接推高索引体积、降低缓存命中率。InnoDB 表中,一个 4 字节 INT 主键和一个 8 字节 (user_id, order_no) 主键对二级索引的影响差不多;但换成 VARCHAR(36) UUID,每个二级索引条目就多压 36 字节,IO 和内存压力陡增。
容易踩的坑:用 (region, status, category, created_at) 做主键,看似“业务唯一”,实则让每个二级索引膨胀 3–4 倍,写入变慢,查询也因缓存失效而变慢。
- 超过 3 个字段时,优先考虑单列自增
id作主键 + 单独建UNIQUE约束保证业务唯一性 - 若必须多字段,确保每个字段都参与高频等值查询,而非仅为“逻辑上不重复”
- 大字段(
VARCHAR(255)、JSON)绝对禁止进入联合主键
避免隐式类型转换与函数包裹导致索引失效
哪怕字段顺序和数量都对,只要 WHERE 条件里对最左字段做了函数操作或类型隐式转换,B+树查找起点就丢失了。例如主键是 (user_id, order_id),但写了 WHERE CAST(user_id AS CHAR) = '123',或 WHERE user_id + 0 = 123,MySQL 就无法走索引。
使用场景:ORM 自动生成 SQL 时容易带出这类写法,比如 Laravel 的 whereRaw 或 Django 的 extra()。
- 检查
EXPLAIN中的key_len:比如主键共 8 字节,但key_len只显示 4,说明第二个字段被跳过了 - 字符串字段比较时注意字符集一致,
utf8mb4_general_ci和utf8mb4_unicode_ci混用可能触发隐式转换 - 时间字段别用
DATE(created_at)包裹,改用范围:created_at >= '2026-01-01' AND created_at
高频范围查询场景下,联合主键未必优于单列主键 + 二级索引
如果业务中大量执行 WHERE created_at BETWEEN ? AND ? ORDER BY created_at DESC LIMIT 20,把 created_at 放进联合主键最左位置,确实能避免排序和回表;但代价是所有二级索引变大、写入更慢、且 user_id 查询无法高效利用聚簇结构。
这时更优解往往是:保留自增 id 主键(维持写入性能),再单独建 INDEX idx_created_at (created_at) 或 INDEX idx_user_created (user_id, created_at) —— 覆盖常用路径,不牺牲通用性。
真正容易被忽略的是:联合主键一旦设定,修改成本极高。大表执行 ALTER TABLE ... ADD PRIMARY KEY (a,b) 会锁表、重建聚簇索引、临时空间占用接近原表两倍。线上系统里,宁可多建几个二级索引,也别轻易动主键结构。











