
EXPLAIN SELECT *
FROM t_user u, user_role ur
WHERE u.id = ur.user_id;🧩 一、分析表格内容
列名 | 含义 |
id | 执行顺序标识,1 表示同一级别的查询 |
select_type | 查询类型,这里都是 SIMPLE(简单查询) |
table | 当前执行的表: |
type | 连接类型 |
possible_keys | 可能使用的索引 |
key | 实际使用的索引 |
key_len | 实际使用的索引长度(字节) |
ref | 哪个字段或常量与索引比较 |
rows | 预估扫描的行数 |
Extra | 额外信息(是否 Using where / index / temporary 等) |
🧠 二、执行顺序与含义
第一行(表 ur = user_role)
项 | 值 |
type | ALL |
possible_keys | fk_ur_user_id |
key | NULL |
key_len | NULL |
Extra | Using where |
说明:
- MySQL 对
user_role表做了全表扫描(ALL); - 虽然它有一个可能的索引
fk_ur_user_id(即user_id上的索引),但没有被使用; -
key_len= NULL 表示没用到索引; -
Using where表示通过条件过滤。
🔍 原因可能是:
优化器认为扫描整个表 + 关联比使用索引更快,或者 user_role.user_id 上的索引选择性不高(重复值太多)。
第二行(表 u = t_user)
项 | 值 |
type | eq_ref |
key | PRIMARY |
key_len | 98 |
ref | test.ur.user_id |
说明:
-
type=eq_ref:最优的连接方式之一(主键等值连接),表示对t_user每次查一条。 -
key=PRIMARY:使用了主键索引; -
key_len=98:主键长度为 98 字节; -
ref=test.ur.user_id:表示关联条件是u.id = ur.user_id; - 表示 MySQL 对
ur表的每一行,根据user_id去t_user查主键(点查)。
📏 三、key_len=98 的解释
这说明 t_user.id 主键占用了 98 个字节。
这通常发生在以下情况之一:
-
id是一个VARCHAR(36)(比如 UUID 字符串),在utf8mb4下每字符最多占 4 字节:36 × 3(或4) ≈ 108 ~ 144 字节,所以 98 是合理的近似。 - 或者
id是较长的字符串类型(非 INT / BIGINT)。
✅ 建议:
如果这个字段只是用来做主键关联,而不是业务展示,可以考虑改为:
id BIGINT UNSIGNED AUTO_INCREMENT或
id BINARY(16) -- 存储 UUID 更紧凑这样索引长度可以从 98 字节降到 8 或 16 字节,性能更好。
⚙️ 四、优化建议
- 确认 user_role.user_id 上是否有索引
- 如果没有,添加索引:
CREATE INDEX idx_user_role_user_id ON user_role(user_id);- 如果已有但没被使用,可能是统计信息不准,可以尝试:
ANALYZE TABLE user_role;- 检查主键类型
- 若主键为 VARCHAR,建议改成整数型或 BINARY。
- 验证优化器决策
- 加上
STRAIGHT_JOIN强制连接顺序,看看是否更优:
EXPLAIN SELECT STRAIGHT_JOIN *
FROM user_role ur, t_user u
WHERE u.id = ur.user_id;✅ 总结
表 | 是否用索引 | key_len | 说明 |
user_role | ❌ 没用 | NULL | 全表扫描 |
t_user | ✅ 用了主键索引 | 98 | 每次通过主键等值查一条 |
key_len=98不代表好坏,只是说明用了一个比较“长”的索引字段。
















