MySQL 索引全景指南:从底层原理到实战优化

64 阅读19分钟

MySQL 索引全景指南:从底层原理到实战优化

一、索引基础:本质与核心价值

索引是数据库的 “智能目录”,通过特定数据结构减少磁盘 I/O 次数,实现 “空间换时间” 的查询优化。其核心价值体现在查询效率的天壤之别:

  • 无索引:全表扫描(时间复杂度 O (n))→ 100 万行数据需 100 万次磁盘 I/O,性能极差
  • 有索引:结构化查找(B + 树时间复杂度 O (log n))→ 100 万行数据仅需 3 次 I/O,效率提升 30 万倍

💡 关键概念:I/O 次数 = 索引树高度InnoDB 默认索引页(节点)大小为 16KB,若主键为 INT(4 字节)+ 子节点指针(6 字节),单节点可存储约 1600 个关键字,树高与数据量的对应关系如下:

数据量B + 树高度最大 I/O 次数
1600 条以内11
1600~256 万条22
256 万~40 亿条33
40 亿~6400 亿条44

二、索引核心结构:为什么 B + 树是最优解?

(一)四种树结构深度对比

树结构结构特点I/O 计算(10 亿数据)适用场景
二叉搜索树易退化链表(有序插入时)10 亿次 I/O完全不适合磁盘存储
红黑树自平衡但单节点存 1 个 key30 次 I/O内存数据(如 Java TreeMap)
B 树内部节点存 key+data4 次 I/O早期数据库(如 Oracle 旧版本)
B + 树内部存 key + 叶子存 data + 双向链表4 次 I/OMySQL 默认(InnoDB/MyISAM)

(二)B + 树的核心优势(磁盘存储黄金标准)

以 10 亿数据的四层 B + 树为例,结构示意图如下:

                    ┌───────────────┐   ← 根节点(1层)
                    │    [200]      │
                    └──────┬────────┘
                           │
        ┌──────────────┬───┴───┬──────────────┐   ← 内部节点(2层)
        ▼              ▼       ▼              ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│   [50, 100]   │ │  [150, 180]   │ │  [220, 250]   │ │  [280, 300]   │
└──────┬────┬───┘ └──────┬────┬───┘ └──────┬────┬───┘ └──────┬────┬───┘
       │    │            │    │            │    │            │    │
       ▼    ▼            ▼    ▼            ▼    ▼            ▼    ▼   ← 叶子节点(3-4层)
┌───────────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│10203040│...│ ⇄ │60708090│...│ ⇄ │110120130140│...│ ⇄ │160170180│...│
└───────────────┘ └──────────────────┘ └──────────────────┘ └──────────────────┘
  1. 空间效率高:内部节点仅存 key,16KB 页可存 1600 个 key,树高显著降低
  2. 查询性能稳:所有查询最终落到叶子节点,路径长度一致(无极端情况)
  3. 范围查询快:叶子节点通过双向链表连接,BETWEEN/ORDER BY无需回溯
  4. 数据存储密:叶子节点连续存储,磁盘预读(按页加载)效率高

三、存储引擎差异:InnoDB vs MyISAM 索引实现

(一)InnoDB:聚簇索引为核心的索引体系

InnoDB 以聚簇索引为基础,所有索引均基于 B + 树实现,内部节点仅存 key,无数据

索引类型叶子节点内容是否回表查询示例I/O 次数(1 亿数据)
主键索引完整数据行❌ 否SELECT * WHERE id=203 次(根→内部→叶子)
二级索引主键值(如 id=20)✅ 是SELECT * WHERE user_id=136 次(3 次二级 + 3 次主键)

🔑 关键逻辑:二级索引需 “回表”—— 先查二级索引树获主键,再查主键索引树获完整数据。

(二)MyISAM:索引与数据分离的非聚簇体系

MyISAM 支持主键索引,但与 InnoDB 的聚簇索引存在本质差异:

  • MyISAM 的主键索引本质是 “带唯一性约束的普通 B + 树索引”,叶子节点存储数据文件的物理地址指针,而非完整数据行
  • 若未显式指定主键,MyISAM 会默认创建一个隐藏的自增列作为主键,但其索引结构仍为非聚簇类型
索引类型叶子节点内容是否回表查询示例I/O 次数(1 亿数据)
主键索引数据文件指针(如 0x100)❌ 否SELECT * WHERE id=204 次(3 次索引 + 1 次数据)
二级索引数据文件指针(如 0x200)❌ 否SELECT * WHERE user_id=134 次(3 次索引 + 1 次数据)

🔑 核心差异:MyISAM 的主键索引无 “聚簇特性”,与二级索引结构一致(仅多唯一性约束);InnoDB 的主键索引是聚簇索引,直接关联完整数据,查询效率更高。

(三)实战对比:相同查询的执行流程

场景SELECT * FROM orders WHERE user_id=13(表含 id=20、30 两条匹配数据)表结构id INT PRIMARY KEY, user_id INT, amount DECIMAL(10,2), INDEX idx_user(user_id)

1. InnoDB 执行流程(需回表)
步骤1:查二级索引idx_user(3次I/O)
  ┌───────────────┐(根节点)→ ┌───────────────┐(内部节点)→ ┌───────────────┐(叶子节点)
  │ user_id=13    │            │ user_id=13    │            │ 主键值: [20,30]│

步骤2:回表查主键索引(3次I/O/id)
  ┌───────────────┐(根节点)→ ┌───────────────┐(内部节点)→ ┌───────────────┐(叶子节点)
  │ id=20         │            │ id=20         │            │ [20,13,200]   │
  │ id=30         │            │ id=30         │            │ [30,13,150]   │

✅ 总I/O:3+3+3=9
2. MyISAM 执行流程(无需回表)
步骤1:查索引idx_user(3次I/O)
  ┌───────────────┐(根节点)→ ┌───────────────┐(内部节点)→ ┌───────────────┐(叶子节点)
  │ user_id=13    │            │ user_id=13    │            │ 指针: [0x100,0x200]│

步骤2:读数据文件(1次I/O,批量加载)
  ┌───────────────────────┐
  │ 0x100→(20,13,200)     │
  │ 0x200→(30,13,150)     │
  └───────────────────────┘

✅ 总I/O:3+1=4

四、索引类型全解析:从基础到特殊

(一)B + 树索引家族(核心类型)

1. 主键索引(InnoDB 聚簇特性 vs MyISAM 普通特性)
  • 定义:以主键字段(PRIMARY KEY)为索引键的 B + 树索引,所有存储引擎均支持,但实现差异显著:

    • InnoDB:主键索引是聚簇索引,叶子节点存储完整数据行,是表的 “核心索引”,一张表仅 1 个
    • MyISAM:主键索引是普通 B + 树索引(仅多唯一性约束),叶子节点存储数据指针,与二级索引结构一致
  • 核心特性

    • 唯一性约束:强制字段值不重复,且不允许NULL(所有存储引擎通用)
    • 索引优先级:查询时优先使用主键索引(优化器默认选择高选择性索引)
  • 实战示例

    sql

    -- 1. InnoDB表(主键索引为聚簇索引)
    CREATE TABLE innodb_users (
        id INT NOT NULL AUTO_INCREMENT COMMENT '用户ID',
        username VARCHAR(50) NOT NULL COMMENT '用户名',
        PRIMARY KEY (id)  -- 聚簇索引,叶子存完整数据
    ) ENGINE=InnoDB;
    
    -- 2. MyISAM表(主键索引为普通索引)
    CREATE TABLE myisam_users (
        id INT NOT NULL AUTO_INCREMENT COMMENT '用户ID',
        username VARCHAR(50) NOT NULL COMMENT '用户名',
        PRIMARY KEY (id)  -- 普通B+树索引,叶子存数据指针
    ) ENGINE=MyISAM;
    
    -- 主键查询对比
    SELECT * FROM innodb_users WHERE id=10086;  -- InnoDB:3次I/O(直接读叶子数据)
    SELECT * FROM myisam_users WHERE id=10086;  -- MyISAM:4次I/O(索引+数据文件)
    
  • 关键结论:“主键索引” 是所有存储引擎的基础功能,但 “InnoDB 的主键索引 = 聚簇索引” 是其特有特性,MyISAM 无聚簇索引,需区分表述避免混淆。

2. 唯一索引(唯一性约束 + 查询优化)
  • 定义:对具有唯一性约束的字段(UNIQUE)建立的 B + 树索引,叶子节点存储主键值(InnoDB)或数据指针(MyISAM),核心作用是保证字段值不重复,同时优化查询

  • 核心特性(关于 NULL 的关键修正)

    • 允许字段值为NULL,且支持多个NULL(因 SQL 标准中NULL表示 “未知”,不与任何值相等,包括自身,故多个NULL不违反唯一性约束)
    • 一张表可创建多个唯一索引(如usernameemail分别建唯一索引)
  • 实战示例(含多 NULL 验证)

    sql

    -- 创建含唯一索引的表(允许email为NULL)
    CREATE TABLE users (
        id INT PRIMARY KEY AUTO_INCREMENT,
        username VARCHAR(50) NOT NULL,
        email VARCHAR(100) COMMENT '允许为NULL',
        -- 唯一索引:username(非NULL)、email(可NULL)
        UNIQUE INDEX idx_unique_username (username),
        UNIQUE INDEX idx_unique_email (email)
    );
    
    -- 1. 插入重复username:报错(违反唯一性约束)
    INSERT INTO users (username, email) VALUES ('zhangsan', 'zhangsan@xxx.com');
    INSERT INTO users (username, email) VALUES ('zhangsan', 'zhangsan2@xxx.com');  -- 错误:Duplicate entry 'zhangsan' for key 'idx_unique_username'
    
    -- 2. 插入多个NULL的email:成功(多个NULL不违反约束)
    INSERT INTO users (username, email) VALUES ('lisi', NULL);
    INSERT INTO users (username, email) VALUES ('wangwu', NULL);  -- 成功:无报错
    INSERT INTO users (username, email) VALUES ('zhaoliu', NULL);  -- 成功:无报错
    
    -- 3. 唯一索引查询(命中索引)
    SELECT id FROM users WHERE username='lisi';  -- 命中idx_unique_username
    SELECT id FROM users WHERE email IS NULL;    -- 命中idx_unique_email,返回lisi、wangwu、zhaoliu的记录
    
  • 适用场景:需保证字段唯一性的场景(如用户名、手机号),或需优化 “可 NULL 字段” 查询的场景(如未绑定邮箱用户的筛选)。

3. 普通单列索引(基础查询优化)
  • 定义:对单个非主键、非唯一字段建立的 B + 树索引,无唯一性约束,叶子节点存储主键值(InnoDB)或数据指针(MyISAM)。

  • 核心特性:仅用于优化查询,不限制字段值重复,支持多值匹配(如WHERE age=25返回所有 25 岁用户)。

  • 实战示例

    sql

    -- 给users表添加普通索引(优化age字段查询)
    ALTER TABLE users ADD INDEX idx_normal_age (age);
    
    -- 普通索引查询(高频单条件场景)
    SELECT username, age FROM users WHERE age=25;  -- 命中索引,且覆盖查询(无需回表)
    SELECT * FROM users WHERE age>30;              -- 命中索引,范围查询(InnoDB需回表)
    
  • 适用场景:无唯一性要求的高频查询字段(如用户年龄、订单状态、商品分类)。

4. 联合索引(复合索引,多条件查询优化)
  • 定义:对多个字段组合建立的 B + 树索引,按字段顺序构建索引键(如(user_id, create_time)),叶子节点存储主键值(InnoDB)或数据指针(MyISAM)。

  • 核心原则:最左前缀原则(查询需从左至右匹配,跳过左列则索引失效)。

  • 实战示例

    sql

    -- 创建订单表,添加联合索引(优化“用户+时间范围”查询)
    CREATE TABLE orders (
        id INT PRIMARY KEY AUTO_INCREMENT,
        user_id INT NOT NULL,
        create_time DATETIME NOT NULL,
        amount DECIMAL(10,2) NOT NULL,
        -- 联合索引:user_id(左1)→ create_time(左2)→ amount(左3)
        INDEX idx_combo_user_time_amount (user_id, create_time, amount)
    );
    
    -- 有效查询(命中索引)
    SELECT * FROM orders WHERE user_id=13;  -- 匹配左1列user_id
    SELECT amount FROM orders WHERE user_id=13 AND create_time>'2024-01-01';  -- 匹配左1+左2列,覆盖查询
    SELECT * FROM orders WHERE user_id=13 AND create_time BETWEEN '2024-01-01' AND '2024-01-31' AND amount>200;  -- 匹配左1+左2+左3列
    
    -- 无效查询(索引失效)
    SELECT * FROM orders WHERE create_time>'2024-01-01';  -- 跳过左1列user_id
    SELECT * FROM orders WHERE user_id=13 AND amount>200;  -- 跳过左2列create_time,仅左1列生效
    
  • 适用场景:高频多条件查询(如 “用户的近期订单”“分类下的热门商品”),通过字段顺序优化过滤效率。

5. 前缀索引(字符字段空间优化)
  • 定义:对字符类型字段(CHAR、VARCHAR、TEXT)的前 N 个字符建立的 B + 树索引,仅存储字段前缀作为索引键,减少索引空间占用。

  • 核心注意:需保证前缀的 “选择性”(即前缀重复率低),避免因前缀重复导致索引过滤效果差。

  • 实战示例

    sql

    -- 场景:用户表email字段为VARCHAR(100),全字段索引占用空间大,建前缀索引
    CREATE TABLE users (
        id INT PRIMARY KEY AUTO_INCREMENT,
        email VARCHAR(100) NOT NULL COMMENT '用户邮箱(如zhangsan_123456@xxx.com)',
        -- 对email前15个字符建前缀索引(通过选择性计算确定N=15)
        INDEX idx_prefix_email (email(15))
    );
    
    -- 计算前缀选择性(判断N是否合适)
    -- 全字段选择性:SELECT COUNT(DISTINCT email)/COUNT(*) FROM users;  -- 假设结果0.99(接近1,选择性高)
    -- 前缀选择性:SELECT COUNT(DISTINCT LEFT(email,15))/COUNT(*) FROM users;  -- 结果0.98,接近全字段,N=15合适
    
    -- 前缀索引查询(命中索引)
    SELECT id FROM users WHERE email='zhangsan_123456@xxx.com';  -- 前缀“zhangsan_123456@”匹配,命中索引
    
  • 适用场景:长字符字段(如邮箱、手机号前缀、长文本标识),全字段索引空间占用过大时,用前缀索引平衡空间与查询效率。

(二)非 B + 树索引(特殊场景)

1. 哈希索引
  • 结构原理:通过哈希函数将 key 映射为哈希值,建立 “哈希值→数据地址” 的映射表。

  • 核心特点

    • 等值查询极快(时间复杂度 O (1)),无 I/O 随数据量增长的问题
    • 不支持范围查询(>、<、BETWEEN)和排序
  • 适用场景:高频等值查询(如用户登录验证WHERE username='xxx'

  • InnoDB 支持:自适应哈希索引(AHI),由 MySQL 自动创建维护,无需手动干预

2. 全文索引
  • 结构原理:采用倒排索引(非 B + 树),通过 “分词→词项映射表” 实现文本检索。

  • 倒排索引结构

    plaintext

    词项(Term) → 文档列表(Document ID + 位置)
    "MySQL"    → [(1, 5), (3, 2), (10, 8)]  -- 存在于ID=1的第5个词、ID=3的第2个词...
    "索引"     → [(1, 8), (2, 3), (5, 10)]
    
  • 关键流程:分词(英文按空格,中文需 ngram 插件)→ 过滤停止词(如 “the”“的”)→ 建立词项映射

  • 使用语法

    sql

    -- 创建全文索引
    ALTER TABLE articles ADD FULLTEXT INDEX idx_title_content (title, content);
    -- 全文检索
    SELECT * FROM articles WHERE MATCH(title, content) AGAINST('MySQL索引' IN NATURAL LANGUAGE MODE);
    
  • 适用场景:博客内容搜索、商品描述检索等文本匹配场景,比LIKE '%关键词%'效率高 10 倍以上

3. 空间索引
  • 结构原理:基于 R 树 / 四叉树,对地理空间数据类型(GEOMETRY、POINT、POLYGON)建立索引。

  • 核心功能:支持空间关系查询(如包含、距离计算)

  • 使用语法

    sql

    -- 创建空间索引
    ALTER TABLE shops ADD SPATIAL INDEX idx_location (location);
    -- 查询1公里内的店铺
    SELECT * FROM shops WHERE ST_Distance_Sphere(location, POINT(116.403874, 39.914885)) < 1000;
    
  • 适用场景:地图应用(外卖配送范围、附近景点推荐)

(三) 聚簇索引与二级索引(InnoDB 索引体系)

  • 聚簇索引:仅 InnoDB 支持,即主键索引,叶子节点存完整数据行,是 InnoDB 的 “主索引”,所有其他索引都依赖聚簇索引。

  • 二级索引:除主键索引外的所有索引(唯一索引、普通索引、联合索引、前缀索引),叶子节点存主键值,查询需 “回表”(通过主键查聚簇索引获完整数据)。

  • 实战对比

    索引类型属于聚簇 / 二级索引叶子节点内容查询示例I/O 次数(1 亿数据)
    主键索引(InnoDB)聚簇索引完整数据行SELECT * WHERE id=100863 次(无需回表)
    唯一索引(username)二级索引主键值(如 id=10086)SELECT * WHERE username='zhangsan'6 次(需回表)
    联合索引(user_id, create_time)二级索引主键值(如 id=5000)SELECT * WHERE user_id=13 AND create_time>'2024-01-01'6 次(需回表)

五、索引实战:设计、优化与避坑

(一)索引设计最佳实践

1. 主键设计原则(结合存储引擎差异)
  • InnoDB 表:优先自增 INT/BIGINT,保证聚簇索引有序插入,避免页分裂;若需业务字段唯一,用 “自增主键 + 唯一索引” 组合:

    sql

    CREATE TABLE innodb_users (
        id INT PRIMARY KEY AUTO_INCREMENT COMMENT '自增聚簇主键',
        user_no VARCHAR(20) NOT NULL COMMENT '业务唯一编号',
        UNIQUE INDEX idx_unique_user_no (user_no)  -- 业务字段用唯一索引
    ) ENGINE=InnoDB;
    
  • MyISAM 表:主键类型无严格限制(因无聚簇特性),但仍建议用自增 INT(减少索引碎片)

  • 拒绝 UUID/GUID:无序性导致索引页频繁分裂,性能下降 30% 以上;拒绝长业务字段(如手机号),避免索引空间浪费。

2. 联合索引字段顺序
  • 高选择性字段放左列:如user_id(选择性 0.9)放create_time(选择性 0.3)左侧,减少后续扫描数据量
  • 高频过滤字段放左列:如高频查询WHERE user_id=13 AND status=1,则user_id在前,status在后
  • 覆盖查询字段:将SELECT字段加入索引,避免回表(如SELECT user_id, amount则建idx_user_amount(user_id, amount)
3. 覆盖索引:InnoDB 二级索引优化核心(含唯一索引示例)
  • 定义:查询字段全部包含在索引中,无需回表查主键索引。

  • 实战示例

    sql

    -- 1. 唯一索引覆盖查询(查询id+username,无需回表)
    SELECT id, username FROM users WHERE username='zhangsan';
    -- 索引:idx_unique_username(username为索引键,叶子存主键id)
    -- 执行:直接从唯一索引获id+username,1亿数据3次I/O(原需6次)
    
    -- 2. 联合索引覆盖查询(查询amount,无需回表)
    SELECT amount FROM orders WHERE user_id=13 AND create_time>'2024-01-01';
    -- 索引:idx_combo_user_time_amount(user_id, create_time, amount)
    -- 执行:查询字段amount在索引中,1亿数据3次I/O(原需6次)
    

(二)索引失效场景全清单(附解决方案)

失效场景示例 SQL失效原因解决方案
索引列用函数 / 运算WHERE YEAR(create_time)=2024函数破坏索引有序性改为WHERE create_time BETWEEN '2024-01-01' AND '2024-12-31'
隐式类型转换WHERE user_id='13'(user_id 为 INT)字符串转 INT 触发全表扫描保证类型一致(WHERE user_id=13
左模糊查询WHERE name LIKE '%john'左模糊无法利用索引有序性改用右模糊(LIKE 'john%')或全文索引
OR 连接非索引列WHERE user_id=13 OR amount>150(amount 无索引)OR 需同时扫描索引与非索引列给 amount 加索引,或拆分为两个查询
使用 NOT IN/!=/IS NOT NULLWHERE status!=1范围过大,索引过滤效率低改用IN(如WHERE status IN (0,2,3)
JOIN 关联字段不匹配orders.user_id(INT)JOIN users.id(VARCHAR)类型转换导致索引失效保证关联字段类型与字符集一致

(三)索引代价与平衡

索引并非越多越好,需平衡查询性能与写入成本:

1. 空间代价
  • 1 亿行数据的 INT 主键索引约占 400MB(1 亿 ×4 字节),VARCHAR (50) 二级索引约占 2GB
  • 单表索引数量建议控制在 5 个以内,避免索引文件超过数据文件体积
2. 写入代价

INSERT/UPDATE/DELETE操作需同步更新所有索引,具体影响如下:

操作类型索引影响性能损耗示例
INSERT所有索引树插入新节点,可能触发页分裂10 个索引比 1 个索引慢 5 倍以上
UPDATE若更新索引字段,需删除旧索引项并插入新项索引字段更新比非索引字段慢 3 倍
DELETE标记索引项为删除,后续需碎片整理大量删除后索引碎片率达 30%+
3. 平衡建议
  • 写入频繁表(如订单表、日志表):仅保留 1-2 个核心查询索引
  • 只读表(如报表表、历史数据表):可适当增加索引
  • 定期清理无用索引:通过sys.schema_unused_indexes查看 30 天未使用的索引,果断删除

(四)用 EXPLAIN 验证索引有效性

EXPLAIN是判断索引是否生效的核心工具,关键字段解读如下:

sql

EXPLAIN SELECT user_id, amount FROM orders WHERE user_id=13;
字段含义理想值
type索引使用类型ref(等值查询)、range(范围查询)
key实际使用的索引名称非 NULL,且为设计的目标索引
rows预估扫描行数越小越好(如 < 100)
Extra额外执行信息Using index(覆盖索引)、Using where(无多余排序)

常见异常情况

  • type=ALL:全表扫描,索引未生效
  • Extra=Using filesort:需额外排序,索引未包含排序字段
  • Extra=Using temporary:需创建临时表,通常因 GROUP BY/ORDER BY 字段无索引

六、知识体系总结:MySQL 索引全景图

plaintext

MySQL索引体系
├─ 基础理论
│  ├─ 本质:空间换时间,减少磁盘I/O
│  ├─ 核心指标:树高=I/O次数,10亿数据4I/O
│  └─ 价值:全表扫描→结构化查找,效率提升百万倍
├─ 核心结构
│  └─ B+树:内部存key+叶子存data+双向链表,磁盘存储最优解
├─ 存储引擎实现
│  ├─ InnoDB:主键索引=聚簇索引(叶子存数据)+ 二级索引(需回表)
│  └─ MyISAM:主键索引=普通B+树(叶子存指针)+ 二级索引(同结构)
├─ 索引类型全谱
│  ├─ B+树家族(核心)
│  │  ├─ 主键索引:所有引擎支持,InnoDB为聚簇型,MyISAM为普通型
│  │  ├─ 唯一索引:支持多NULL,保证唯一性+优化查询
│  │  ├─ 普通索引:无约束,仅优化单条件查询
│  │  ├─ 联合索引:多字段组合,遵循最左前缀原则
│  │  ├─ 前缀索引:长字符优化,需计算选择性
│  │  └─ 二级索引:InnoDB中非聚簇索引,需回表
│  └─ 特殊索引
│     ├─ 哈希索引:等值快,不支持范围
│     ├─ 全文索引:倒排结构,文本检索
│     └─ 空间索引:R树/四叉树,地理查询
├─ 实战设计
│  ├─ 主键:InnoDB用自增INT+唯一索引,MyISAM避免长字段
│  ├─ 联合索引:高选择性+高频字段放左,覆盖查询字段
│  └─ 避坑:避免函数运算、隐式转换、左模糊
└─ 平衡优化
   ├─ 代价控制:单表索引≤5个,写入表少建索引
   ├─ 验证工具:EXPLAIN看type/key/Extra
   └─ 定期维护:删无用索引,清碎片

终极结论

MySQL 索引的核心是B + 树结构场景化选择,需重点关注存储引擎差异与约束细节:

  1. 主键索引是所有引擎的基础,但 InnoDB 的聚簇特性是其查询效率核心,MyISAM 无此特性,设计时需针对性选型;
  2. 唯一索引支持多个 NULL 值,需修正 “仅允许 1 个 NULL” 的认知,避免业务逻辑错误;
  3. 日常查询优先用 B + 树索引家族,通过覆盖索引优化 InnoDB 二级查询,特殊场景(文本、地理)用非 B + 树索引;
  4. 索引设计需平衡性能与代价,通过 EXPLAIN 验证有效性,定期维护优化。