MySQL 支持的各类索引

测试表

复制代码
-- study_test.sales_data definition

CREATE TABLE `sales_data` (
  `id` int NOT NULL AUTO_INCREMENT,
  `product_id` int DEFAULT NULL COMMENT '产品ID',
  `region` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci DEFAULT NULL COMMENT '销售区域',
  `sale_date` date DEFAULT NULL COMMENT '销售日期',
  `amount` decimal(10,2) DEFAULT NULL COMMENT '销售金额',
  `category` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci DEFAULT NULL COMMENT '产品类别',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=49149011 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_ci;

一、索引的本质与作用

索引是数据库的"目录" ,通过特定数据结构(如B+树)对表中一列或多列的值进行排序存储,加速数据检索并减少磁盘I/O。没有索引的查询就像在图书馆无目录找书,只能全表扫描(Full Table Scan)。

复制代码
-- 创建基础索引语法
CREATE [UNIQUE|FULLTEXT] INDEX index_name 
ON table_name (column1 [ASC|DESC], ...);

二、MySQL 索引类型详解

索引类型 底层数据结构 经典示例(基于sales_data表) 适用场景与关键特性
B-Tree索引​ (默认) B+Tree CREATE INDEX idx_sale_date ON sales_data(sale_date); SELECT * FROM sales_data WHERE sale_date = '2024-12-26'; 最通用 的索引。支持精确匹配、范围查询(><BETWEEN)、排序(ORDER BY)和分组(GROUP BY)。除全文索引外,下文讨论的索引类型(如唯一、主键、复合索引)默认都使用B+Tree结构。
全文索引​ (FULLTEXT) 倒排索引 ALTER TABLE sales_data ADD FULLTEXT ft_category(category); SELECT * FROM sales_data WHERE MATCH(category) AGAINST('Electronics' IN NATURAL LANGUAGE MODE); 专门用于文本内容的全文搜索 。适合在 CHARVARCHARTEXT类型列上进行关键词检索相关性排序 ,例如搜索产品类别中包含特定关键词的记录。它解决了 LIKE '%keyword%'导致的性能问题,但默认对中文分词支持较弱。
主键索引​ (PRIMARY KEY) B+Tree (聚簇索引) id列已是主键,无需额外创建。表数据文件本身即是按主键组织的B+树。 唯一且非空 。一个表只能有一个。强烈建议使用整型自增主键,可以有效减少页分裂,提高数据写入效率。
唯一索引​ (UNIQUE) B+Tree CREATE UNIQUE INDEX uk_product ON sales_data(product_id, region, sale_date); 确保(product_id, region, sale_date)组合唯一。 保证列或列组合的唯一性,允许NULL值。在等值查询时性能极高。
单列索引 B+Tree CREATE INDEX idx_amount ON sales_data(amount); 适合查询条件高度集中在某个单一字段上。
复合索引​ (联合索引) B+Tree CREATE INDEX idx_region_date_cat ON sales_data(region, sale_date, category); 最左前缀原则 :查询必须从索引最左列开始。例如,上述索引对 WHERE region='East'WHERE region='East' AND sale_date > '2024-01-01'有效,但对 WHERE sale_date > '2024-01-01'无效。
覆盖索引​ (Covering Index) 特殊的B+Tree使用方式 CREATE INDEX idx_cover_query ON sales_data(region, sale_date, amount); SELECT region, sale_date, SUM(amount) FROM sales_data WHERE region='East' GROUP BY region, sale_date; 一个索引包含了查询所需的所有字段(Extra列显示 Using index)。性能极佳,因为数据库引擎只需扫描索引即可返回数据,无需回表查询数据行。

1. 主键索引(PRIMARY KEY)

复制代码
ALTER TABLE sales_data ADD PRIMARY KEY (id); -- 表已定义,此处演示语法
  • 特点:唯一且非空,一个表只能有一个

  • 底层:聚簇索引(InnoDB),数据按主键物理排序

  • 失效场景 :几乎不会失效,但WHERE id IS NULL无效(主键非空)

2. 唯一索引(UNIQUE)

复制代码
CREATE UNIQUE INDEX uk_product_region ON sales_data (product_id, region);
  • 作用:保证列组合唯一性(可为NULL)

  • 案例:防止同一产品在同一区域重复录入

  • 失效WHERE product_id IS NULL可使用索引(NULL值特殊处理)

3. 普通索引(INDEX)

复制代码
CREATE INDEX idx_sale_date ON sales_data(sale_date);
  • 最常见索引,加速等值/范围查询

  • 失效场景

    复制代码
    WHERE YEAR(sale_date) = 2024; -- 对列使用函数
    WHERE sale_date + INTERVAL 1 DAY > '2024-01-01'; -- 列参与计算

4. 联合索引(Composite Index)

复制代码
CREATE INDEX idx_region_date_cat ON sales_data (region, sale_date, category);
  • 最左前缀原则实战

    • 有效查询:

      复制代码
      WHERE region = 'East'
      WHERE region = 'East' AND sale_date > '2024-01-01'
      WHERE region = 'East' AND sale_date > '2024-01-01' AND category = 'Electronics'
    • 失效查询:

      复制代码
      WHERE sale_date > '2024-01-01' -- 缺少最左列region
      WHERE category = 'Electronics' -- 跳过前两列
      WHERE region = 'East' AND category = 'Electronics' -- 跳过了sale_date(部分失效)

5. 全文索引(FULLTEXT)

复制代码
ALTER TABLE sales_data ADD FULLTEXT ft_category(category);
  • 适用场景 :文本关键词搜索(替代低效的LIKE '%xxx%'

  • 查询示例

    复制代码
    SELECT * FROM sales_data 
    WHERE MATCH(category) AGAINST('+Phone -Apple' IN BOOLEAN MODE);
  • 限制

    • 仅支持MyISAM和InnoDB(MySQL 5.6+)

    • 默认最小词长度4(可通过ft_min_word_len调整)

    • 中文分词问题:需使用ngram解析器(MySQL 5.7+)

      复制代码
      CREATE FULLTEXT INDEX ft_category_cn ON sales_data(category) WITH PARSER ngram;

6. 覆盖索引(Covering Index)

  • 不是独立索引类型,而是高效使用索引的策略

  • 核心思想:索引包含查询所需全部字段,避免回表

    复制代码
    -- 创建覆盖索引
    CREATE INDEX idx_cover_region_date_amount ON sales_data (region, sale_date, amount);
    
    -- 覆盖索引查询(EXPLAIN显示Using index)
    SELECT region, SUM(amount) 
    FROM sales_data 
    WHERE sale_date BETWEEN '2024-01-01' AND '2024-01-31'
    GROUP BY region;

三、索引失效的八大陷阱及解决方案

失效场景 示例 解决方案
对索引列进行计算 WHERE amount * 0.8 > 1000 改为WHERE amount > 1000/0.8
使用函数处理索引列 WHERE LEFT(region, 2) = 'EA' 使用前缀索引:WHERE region LIKE 'EA%'
隐式类型转换 WHERE product_id = '1001'(id为整型) 保持类型一致:WHERE product_id = 1001
OR连接非索引列 WHERE region = 'East' OR amount > 1000 改为UNION:SELECT ... WHERE region='East' UNION ALL SELECT ... WHERE amount>1000
LIKE左模糊匹配 WHERE category LIKE '%Phone' 改用全文索引或右模糊'Phone%'
联合索引跳过最左列 INDEX(a,b,c)+ WHERE b=1 AND c=2 调整查询条件或创建新索引(b,c)
优化器放弃索引 查询结果集 > 表数据20%-30% 增加精确条件或强制索引FORCE INDEX
索引列使用!=或NOT IN WHERE region != 'West' 改写为region IN ('East','North')

以下是导致索引失效的典型场景及应对策略,核心原理是索引(通常是B+Tree)是基于列的原始值有序存储的,任何破坏这种有序性的操作都可能使索引无法被有效利用。

1.对索引列进行操作(计算、函数、类型转换)

  • 失效示例SELECT ... WHERE YEAR(sale_date) = 2024;(对列使用函数)SELECT ... WHERE amount + 100 > 500;(对列进行计算)SELECT ... WHERE product_id = '123';(如果product_id是整型,条件用了字符串,导致类型转换)

  • 解决方案将操作施加于常量值,而非索引列SELECT ... WHERE sale_date BETWEEN '2024-01-01' AND '2024-12-31';``SELECT ... WHERE amount > 400;``SELECT ... WHERE product_id = 123;

2.违反最左前缀原则(针对复合索引)

  • 失效示例 :对于索引 (region, sale_date, category),以下查询无法充分利用索引:WHERE sale_date > '2024-01-01'(跳过了最左的 region列)WHERE category = 'Electronics'(跳过了前两列)WHERE region = 'East' AND category = 'Electronics'(跳过了中间的 sale_date列,此情况只能用到 region索引部分)

  • 解决方案 :设计复合索引时,考虑查询习惯。将区分度高(唯一值多)的常用查询列放在左边。若无法避免,可为特定查询创建新的单列或复合索引。

3.使用模糊查询 LIKE以通配符 %开头

  • 失效示例SELECT ... WHERE category LIKE '%phone%';WHERE category LIKE '%Phone';

  • 解决方案 :尽量避免前缀通配符。如果必须前缀匹配,考虑使用全文索引(FULLTEXT) 。后缀通配符可以利用索引:WHERE category LIKE 'Apple%';

4.使用否定条件或不等于操作符

  • 失效示例SELECT ... WHERE region != 'East';``SELECT ... WHERE product_id NOT IN (1,2,3);

  • 解决方案尽量改写为肯定查询 。例如,region != 'East'可尝试改为 region IN ('North', 'South', 'West')。如果无法避免,优化器在数据量大时可能选择全表扫描,因为否定条件通常需要扫描大部分数据。

5.OR 连接不同索引列

  • 失效示例SELECT ... WHERE region = 'East' OR amount > 1000;(如果 amount无索引,整个查询可能无法使用索引)

  • 解决方案 :使用 UNIONUNION ALL替代:(SELECT ... WHERE region = 'East') UNION ALL (SELECT ... WHERE amount > 1000);。或者,为相关列创建复合索引。

(6)查询结果集过大

  • 失效示例 :在一个有1亿行数据的表中,SELECT ... WHERE category = 'Common';而 'Common' 类别占了90%的数据。

  • 解决方案 :当MySQL优化器判断通过索引回表查询的成本高于直接顺序扫描全表时,会放弃使用索引。此时应增加更精确的查询条件以缩小结果集。

四、索引选择与优化最佳实践

  1. 索引不是越多越好

    每个索引都会占用磁盘空间,并在数据插入、更新、删除时带来维护开销。需要在查询性能与写入性能之间取得平衡。

  2. 使用 EXPLAIN诊断查询

    在SQL语句前加上 EXPLAIN命令,查看执行计划。重点关注 type列(访问类型,应避免ALL全表扫描)、key列(实际使用的索引)、Extra列(是否出现Using filesortUsing temporary等性能隐患)。

  3. 优先考虑复合索引,并注意列顺序

    复合索引一索引多用,且比多个单列索引更高效。设计原则:等值查询列在前,范围查询列在后 。例如,常见查询是 WHERE region='East' AND sale_date BETWEEN ... AND ...,那么索引 (region, sale_date)是最优的。

  4. 尽力使用覆盖索引

    在设计索引时,如果某些查询只需要返回少数几个字段,可以尝试创建一个包含这些查询字段的复合索引,使其成为覆盖索引,避免回表,能极大提升性能。

  5. 全文索引(FULLTEXT)的使用要点

    • 适用场景 :文章内容搜索、产品描述关键词搜索、日志分析等文本检索

    • 创建语法CREATE FULLTEXT INDEX index_name ON table_name(column_name);

    • 查询语法 :必须使用 MATCH(column) AGAINST('keyword'),而非 LIKE

    • 模式选择

      • IN NATURAL LANGUAGE MODE(自然语言模式,默认):计算相关性得分。

      • IN BOOLEAN MODE(布尔模式):支持+(必须包含)、-(必须排除)等操作符,实现复杂逻辑。

    • 局限性 :默认不支持中文等无空格分隔语言的分词(可借助ngram插件),不适合短文本、高频写入场景。

五、EXPLAIN执行计划深度解析

复制代码
EXPLAIN 
SELECT region, AVG(amount) 
FROM sales_data 
WHERE category = 'Electronics' 
GROUP BY region;

关键字段解读:

  • type

    • system> const> eq_ref> ref> range> index> ALL

    • 出现indexALL需优化

  • key:实际使用的索引(NULL表示未用索引)

  • rows:预估扫描行数(越小越好)

  • Extra

    • Using index:覆盖索引,最佳状态

    • Using temporary:需创建临时表(GROUP BY无索引时常见)

    • Using filesort:额外排序(需优化ORDER BY)

六、索引优化高级策略

  1. 三星索引原则(Three-Star Index)

    • ⭐ WHERE条件等值匹配列放最左

    • ⭐⭐ ORDER BY/GROUP BY列顺序与索引一致

    • ⭐⭐⭐ SELECT列被索引覆盖

  2. 索引下推优化(ICP,Index Condition Pushdown)

    • MySQL 5.6+特性,将WHERE条件过滤提前到存储引擎层

    • 减少回表次数,对联合索引效果显著

  3. 索引跳跃扫描(Skip Scan)

    • MySQL 8.0+特性,即使未指定最左列,也能利用索引

    • 示例:索引(gender, age),查询WHERE age > 30(gender未指定)

  4. 索引优化四定律

    • 精准定位原则:WHERE条件列优先建索引

    • 左前缀优先原则:联合索引按区分度降序排列

    • 最小空间原则:整型索引优于字符串索引

    • 覆盖索引优先:避免回表查询

终极心法 :索引不是越多越好!监控慢查询日志(slow_query_log),定期使用OPTIMIZE TABLE重整表空间,才能持续保持数据库高性能。

相关推荐
程序员根根2 小时前
MySQL 事务全解析:从 ACID 特性到实战落地(部门 - 员工场景)
数据库·后端
黑白极客2 小时前
mysql的 order by是怎么工作的?redo-log和binlog为什么采用双确认机制?
数据库·mysql
程序员水自流2 小时前
MySQL常用内置函数详细介绍
java·数据库·mysql
高老庄小呆子2 小时前
SpringBoot3.5.4 引入Knife4j的官方start包
spring
慌糖2 小时前
开发当中常见注解备注
数据库·sql
廋到被风吹走2 小时前
【Spring】Spring Boot详细介绍
java·spring boot·spring
小韩博2 小时前
PHP-MySQL 数据请求与 SQL 注入多样性(小迪 43 课笔记整理)
sql·mysql·php
期待のcode2 小时前
Java中的继承
java·开发语言
TAEHENGV2 小时前
关于应用模块 Cordova 与 OpenHarmony 混合开发实战
android·javascript·数据库