什么是前缀索引 · B+ 树结构 · 创建方法 · 长度选择 · 优缺点 · 实战案例
767 ÷ 4 = 191 字符。超过这个长度就必须用前缀索引。
普通索引存完整列值,前缀索引只存前 N 个字符。B+ 树中的存储结构完全相同,区别仅在于每个索引键的长度。
当 WHERE 条件是完整值时,如 WHERE email = 'user001@example.com',MySQL 自动转换为 WHERE LEFT(email, N) = LEFT('user001@example.com', N) 再走索引。
在 B+ 树中按字典序比较前 N 个字符,找到目标叶子节点。如果多个值的开头相同(如 user001@example 和 user001_other),它们会落在同一条目下。
通过索引找到 row_id 后,用主键回表读取完整行,验证完整列值是否匹配。前缀重复度越高,需要回表的次数越多,性能损失越大。
如果 SELECT 的列都在前缀索引中(覆盖索引),则无需回表,效率接近全列索引。
-- 在已有表上创建前缀索引 CREATE INDEX idx_email_prefix ON users(email(20)); -- 为多个列创建前缀索引 CREATE INDEX idx_email_prefix ON users(email(20)); CREATE INDEX idx_name_prefix ON users(name(10)); -- 查看索引(Sub_part 列即为前缀长度,NULL = 全列) SHOW INDEX FROM users;
-- 通过 ALTER TABLE 添加前缀索引 ALTER TABLE orders ADD INDEX idx_remark_prefix(remark(50)); -- 唯一前缀索引 ALTER TABLE users ADD UNIQUE INDEX idx_email_unique(email(30)); -- 删除索引 ALTER TABLE users DROP INDEX idx_email_prefix; -- 修改长度:先删后建 ALTER TABLE users DROP INDEX idx_email_prefix; ALTER TABLE users ADD INDEX idx_email_prefix(email(25));
-- 建表时直接定义前缀索引 CREATE TABLE posts ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(255) NOT NULL, content TEXT, author_email VARCHAR(100), INDEX idx_title_prefix(title(30)), INDEX idx_email_prefix(author_email(20)) ) ENGINE=InnoDB; -- BLOB / TEXT 列的前缀索引(必须指定长度) CREATE TABLE comments ( id INT PRIMARY KEY AUTO_INCREMENT, body TEXT, INDEX idx_body_prefix(body(100)) ) ENGINE=InnoDB;
-- 表总行数 SELECT COUNT(*) AS total FROM users; -- total = 10000 -- 测试不同前缀长度的区分度(Cardinality) SELECT 5 AS prefix_len, COUNT(DISTINCT LEFT(email, 5)) AS distinct_cnt, COUNT(DISTINCT LEFT(email, 5)) / 10000 AS selectivity UNION ALL SELECT 10, COUNT(DISTINCT LEFT(email, 10)), COUNT(DISTINCT LEFT(email, 10)) / 10000 UNION ALL SELECT 15, COUNT(DISTINCT LEFT(email, 15)), COUNT(DISTINCT LEFT(email, 15)) / 10000 UNION ALL SELECT 20, COUNT(DISTINCT LEFT(email, 20)), COUNT(DISTINCT LEFT(email, 20)) / 10000 UNION ALL SELECT 25, COUNT(DISTINCT LEFT(email, 25)), COUNT(DISTINCT LEFT(email, 25)) / 10000 UNION ALL SELECT 30, COUNT(DISTINCT LEFT(email, 30)), COUNT(DISTINCT LEFT(email, 30)) / 10000 ORDER BY prefix_len;
| 前缀长度 | 不同值数量 | 选择性 | 与全列比 | 推荐 |
|---|---|---|---|---|
| 5 字符 | 200 | 0.020 | 2% | 太低 |
| 10 字符 | 2,800 | 0.280 | 28% | 偏低 |
| 15 字符 | 6,500 | 0.650 | 65% | 可接受 |
| 20 字符 | 9,200 | 0.920 | 92% | ✅ 推荐 |
| 25 字符 | 9,800 | 0.980 | 98% | ✅ 最佳 |
| 30 字符(全列) | 10,000 | 1.000 | 100% | 基准 |
WHERE email > 'user' 无法利用前缀索引| 查询场景 | 全列索引 | 前缀索引 |
|---|---|---|
等值查询 WHERE col = 'xxx' | ✅ 支持 | ✅ 支持 |
前缀 LIKE WHERE col LIKE 'abc%' | ✅ 支持 | ✅ 支持 |
后缀/中间 LIKE LIKE '%abc' / LIKE '%abc%' | ❌ 无法 | ❌ 无法 |
范围查询 WHERE col > 'abc' | ✅ 支持 | ❌ 无法 |
| ORDER BY | ✅ 支持 | ❌ 无法 |
| GROUP BY | ✅ 支持 | ❌ 无法 |
| 覆盖索引 | ✅ 支持 | ⚠️ 有限 |
| 最左前缀原则(复合索引) | ✅ 支持 | ⚠️ 需连续 |
MD5(col) 或 CRC32(col) 建哈希列索引,支持等值查询但不支持范围/排序ALTER TABLE t ADD COLUMN col_hash VARCHAR(32) AS (LEFT(col,20)) STORED,再对哈希列建索引拖动滑块,观察前缀长度变化对选择性和索引体积的影响(以 email 字段为例,总行数 10000,不同值 9800):
-- 1. 创建前缀索引 CREATE INDEX idx_email_prefix ON users(email(20)); -- 2. 测试前缀长度(email 场景) SELECT prefix_len, distinct_cnt, ROUND(distinct_cnt / 10000.0, 3) AS selectivity FROM ( SELECT 5 AS prefix_len, COUNT(DISTINCT LEFT(email,5)) AS distinct_cnt FROM users UNION ALL SELECT 10, COUNT(DISTINCT LEFT(email,10)) FROM users UNION ALL SELECT 15, COUNT(DISTINCT LEFT(email,15)) FROM users UNION ALL SELECT 20, COUNT(DISTINCT LEFT(email,20)) FROM users UNION ALL SELECT 25, COUNT(DISTINCT LEFT(email,25)) FROM users UNION ALL SELECT 30, COUNT(DISTINCT LEFT(email,30)) FROM users ) t ORDER BY prefix_len; -- 3. 验证索引是否生效 EXPLAIN SELECT * FROM users WHERE email LIKE 'user001%'; -- 4. 查看已创建的前缀索引 SHOW INDEX FROM users; -- Sub_part 列即为前缀长度 -- 5. 删除前缀索引 DROP INDEX idx_email_prefix ON users;