一、什么是索引长度?

索引长度指的是 MySQL 在构建索引时,为每个索引列所使用的字节数量。对于字符串类型(如 CHARVARCHARTEXTBLOB 等),可以指定只对前 N 个字符建立索引,这个 N 就是“前缀长度”(Prefix Length),对应的字节数即为该列在索引中的实际长度。

例如:

CREATE INDEX idx_name ON users(name(10));

表示只对 name 字段的前 10 个字符建立索引。

二、为什么需要关注索引长度?

1. 存储空间优化

  • 索引越长,占用磁盘和内存越多。
  • 对于大字段(如 VARCHAR(255)TEXT),全列索引可能非常浪费资源。

2. 性能影响

  • 较短的索引意味着 B+ 树层级更少、节点更紧凑,提高缓存命中率。
  • 但索引过短可能导致选择性(Selectivity)降低,即无法有效区分不同记录,反而降低查询效率。

3. 索引创建限制

  • MySQL 对单个索引的总长度有限制(与存储引擎有关):
    • InnoDB:默认最大索引长度为 767 字节(MySQL 5.6 及之前);
    • MySQL 5.7+ / 8.0:若启用 innodb_large_prefix=ON 且行格式为 DYNAMICCOMPRESSED,则最大可支持 3072 字节
    • MyISAM:最大索引长度为 1000 字节

⚠️ 超出限制会导致建索引失败:ERROR 1071 (42000): Specified key was too long…

如何查看行格式

常见的行格式类型:

  • COMPACT: 紧凑格式,MySQL 5.0 默认
  • REDUNDANT: 冗余格式
  • DYNAMIC: 动态格式,MySQL 5.7.9+ 默认
  • COMPRESSED: 压缩格式

方法1. 使用 SHOW TABLE STATUS 命令

-- 查看所有表的状态信息(包含行格式)
SHOW TABLE STATUS LIKE '表名';

-- 或查看指定数据库的所有表
SHOW TABLE STATUS FROM 数据库名;

示例:

SHOW TABLE STATUS LIKE 'users';
-- 查看结果中的 `Row_format` 列

方法2. 使用 SHOW CREATE TABLE 命令

SHOW CREATE TABLE 表名;

情况一:建表时指定了行格式

-- 建表时明确指定
CREATE TABLE users (
  id INT PRIMARY KEY,
  name VARCHAR(100)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;

使用 SHOW CREATE TABLE users; 会显示:

CREATE TABLE `users` (
  `id` int(11) NOT NULL,
  `name` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 **ROW_FORMAT=DYNAMIC**

情况二:建表时未指定行格式

-- 建表时未指定行格式
CREATE TABLE users (
  id INT PRIMARY KEY,
  name VARCHAR(100)
) ENGINE=InnoDB;

使用 SHOW CREATE TABLE users; 会显示:

CREATE TABLE `users` (
  `id` int(11) NOT NULL,
  `name` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

注意:结果中不会显示 ROW_FORMAT= 部分

为什么会有这种差异?

  1. SHOW CREATE TABLE 显示的是原始建表语句
    • 它不会添加默认值
    • 只显示用户明确指定的选项
    • 这是 MySQL 设计上的行为,保持与原始语句一致
  2. 实际使用的是默认行格式
    • 虽然没有显示,但表确实有一个行格式
    • 使用的是 InnoDB 的默认行格式:
      • MySQL 5.7.9+:默认是 DYNAMIC
      • MySQL 5.7.9 之前:默认是 COMPACT

查看默认行格式

-- 查看当前会话的默认行格式
SHOW VARIABLES LIKE 'innodb_default_row_format';

-- 或使用SELECT查询
SELECT @@innodb_default_row_format;

方法3. 查询 INFORMATION_SCHEMA 系统表

-- 查看特定表的行格式
SELECT
    TABLE_SCHEMA AS '数据库',
    TABLE_NAME AS '表名',
    ROW_FORMAT AS '行格式',
    TABLE_ROWS AS '行数',
    AVG_ROW_LENGTH AS '平均行长度'
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = '数据库名'
  AND TABLE_NAME = '表名';

-- 查看数据库所有表的行格式
SELECT
    TABLE_NAME,
    ROW_FORMAT,
    ENGINE
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = '数据库名';

方法4. MySQL 8.0 新增的查询方式

-- 使用 PERFORMANCE_SCHEMA(如果需要性能相关信息)
SELECT * FROM performance_schema.table_handles
WHERE OBJECT_SCHEMA = '数据库名'
  AND OBJECT_NAME = '表名';

特别注意

使用 DESC 命令(不直接显示行格式)

DESC 表名;  -- 这只显示列信息,不显示行格式 

三、字符集与字节长度的关系

索引长度以字节为单位,而前缀长度以字符为单位。不同字符集下,一个字符占用的字节数不同:

字符集 单字符最大字节数
latin1 1
utf8 3
utf8mb4 4

例如:

  • VARCHAR(255) 使用 utf8mb4 → 最多 255 × 4 = 1020 字节
  • 若尝试对整个列建索引,在旧版 InnoDB(767 限制)下会失败(MySQL 5.6 及之前)。

因此,使用 utf8mb4 时需特别注意前缀长度:

-- 安全做法(767 / 4 ≈ 191)
CREATE INDEX idx_content ON articles(content(191));

四、如何选择合适的前缀长度?

1. 评估选择性(Selectivity)

选择性 = 唯一索引值数量 / 总行数。越高越好(接近 1)。

一、“选择性(Selectivity)”解释

  • 定义

    选择性 = 不同值的数量 ÷ 总行数
    它衡量一个索引能多好地区分不同的记录

  • 举例说明

    • 假设一张用户表有 10,000 行。
    • 如果 email 字段有 9,800 个不同的值 → 选择性 = 9800 / 10000 = 0.98(很高 ✅)
    • 如果 gender 字段只有 “男”、“女” 两种值 → 选择性 = 2 / 10000 = 0.0002(很低 ❌)

✅ 选择性越接近 1,说明这个字段(或前缀)越“独特”,越适合作为索引。

二、为什么对“前 N 个字符”也要看选择性?

因为:

  • VARCHAR(255) 这样的长字段,全列建索引太占空间
  • 但只取前 1 个字符(比如都取 'a')可能所有值都一样 → 索引没用。
  • 所以我们要找一个最小的 N,使得前 N 个字符已经足够“区分”大部分数据。

三、可通过 SQL 估算

-- 计算前 N 个字符的选择性
SELECT
  COUNT(DISTINCT LEFT(column_name, N)) / COUNT(*) AS selectivity
FROM table_name;

拆解:

  • LEFT(column_name, N):取出每行该字段的前 N 个字符
  • COUNT(DISTINCT ...):统计这些前缀中有多少不同的值
  • COUNT(*):总共有多少行。
  • 相除 → 得到前 N 个字符的选择性

举个例子:
假设 url 字段有以下数据(每个url字段的长度是100)(共 5 行):

https://www.example.com/page1……
https://www.example.com/page2……
https://www.google.com/search……
https://www.github.com/user……
https://www.example.com/page3……

  • 当 N = 10:
    • 前10字符都是 https://www → 只有 1 个唯一值 → 选择性 = 1/5 = 0.2
  • 当 N = 20:
    • 前20字符:
      • https://www.example.c
      • https://www.example.c
      • https://www.google.co
      • https://www.github.com
      • https://www.example.c
    • 唯一值有 3 个 → 选择性 = 3/5 = 0.6
  • 当 N = 25:
    • 能区分出 4 个不同前缀 → 选择性 = 4/5 = 0.8
  • 当 N = 30:
    • 几乎全部不同 → 选择性 ≈ 1.0

👉 可以发现:N=25 或 30 就足够了,没必要对整个 100+ 字符的 URL 建索引。
逐步增加 N,直到选择性趋于稳定(如 > 0.9),即可确定合理前缀长度。

2. 业务语义考虑

  • 某些字段前几位已具高区分度(如身份证前6位、邮箱域名等)。
  • 日志或 URL 字段可能前几十字符就足够区分。

五、不同数据类型的索引长度处理

数据类型 是否支持前缀索引 说明
CHAR/VARCHAR 推荐使用前缀索引
TEXT/BLOB ✅(必须指定前缀) 不能对全文建立索引,必须指定前缀长度
INT/DATE 固定长度,无需前缀

注意:TEXT 和 BLOB 列必须指定前缀长度才能建索引。

六、查看索引实际长度的方法

1. 使用 SHOW CREATE TABLE

SHOW CREATE TABLE users;
-- 输出中会显示类似:KEY `idx_name` (`name`(10))

2. 查询 INFORMATION_SCHEMA.STATISTICS

SELECT
  COLUMN_NAME,
  SUB_PART  -- 即前缀长度(字符数),NULL 表示全列
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = 'your_db'
  AND TABLE_NAME = 'your_table'
  AND INDEX_NAME = 'your_index';

七、最佳实践建议

  1. 优先使用 utf8mb4(支持 emoji),但注意索引长度限制。
  2. 避免对大字段全列建索引,优先评估前缀长度。
  3. 对于超长文本,考虑使用全文索引(FULLTEXT)或外部搜索引擎(如 Elasticsearch)

八、示例:解决“Key too long”错误

问题一:单索引太长

CREATE TABLE t1 (
  content VARCHAR(1000)
) CHARSET=utf8mb4;

CREATE INDEX idx_content ON t1(content); -- 报错:Key too long
ERROR 1071 (42000): Specified key was too long; max key length is 3072 bytes

解决方案

-- 使用前缀索引
CREATE INDEX idx_content ON t1(content(191)); -- 191*4=764 < 3072

问题二:联合索引太长

CREATE TABLE t2 (
  a VARCHAR(1000),
  b VARCHAR(1000),
  c VARCHAR(1000)
) CHARSET=utf8mb4;

mysql> CREATE INDEX idx_abc ON t2(a, b, c);          
ERROR 1071 (42000): Specified key was too long; max key length is 3072 bytes

解决方案

-- 使用前缀索引
CREATE INDEX idx_abc ON t2(a(200), b(200), c(200)); -- 200*4*3=2400 < 3072
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐