MySQL索引优化

索引是 MySQL 性能优化的核心,但建了索引≠能用上索引。本文从数据库优化方案、EXPLAIN 执行计划详解、海量数据构造、索引失效场景、关联查询 / 排序 / 分组优化等维度,手把手教你搞定索引优化,让 SQL 查询起飞。

一、数据库整体优化方案

数据库性能瓶颈通常来自这几类,对应解决方案一目了然:

  1. 索引失效 :没有合理创建 / 使用索引 → 索引优化
  2. 关联过多 JOIN :表设计或查询不合理 → SQL 语句优化
  3. 数据量过大 (单表 500W+/2GB+)→ 分库分表
  4. 服务器配置不合理调整 my.cnf 参数(缓冲、线程数等)

二、性能分析神器:EXPLAIN 执行计划

2.1 什么是 EXPLAIN

使用EXPLAIN + SQL语句可以模拟 MySQL 优化器执行查询,清晰看到 SQL 的执行路径、索引使用情况、性能瓶颈,是索引优化的必备工具。

2.2 测试数据准备

复制代码
USE atguigudb;
-- 创建测试表
CREATE TABLE t1(id INT AUTO_INCREMENT, content VARCHAR(100), PRIMARY KEY(id));
CREATE TABLE t2 LIKE t1;
CREATE TABLE t3 LIKE t1;
CREATE TABLE t4(id INT AUTO_INCREMENT, content1 VARCHAR(100), content2 VARCHAR(100), PRIMARY KEY(id));
-- 创建普通索引
CREATE INDEX idx_content1 ON t4(content1);
-- 插入测试数据
INSERT INTO t1(content) VALUES(CONCAT('t1_',FLOOR(1+RAND()*1000)));

2.3 EXPLAIN 关键字段详解

1. id

表示查询中SELECT的执行顺序,是优化核心:

  • id 相同:从上往下顺序执行
  • id 不同:id 越大,优先级越高,越先执行
  • id 为 NULL :执行UNION合并结果集

2. table

表示当前行数据对应的数据表,多表查询中可区分驱动表被驱动表

3. type(★核心优化目标)

查询访问类型,性能从好到坏system > const > eq_ref > ref > range > index > ALL

优化目标 :至少达到range,推荐ref,力争const

  • ALL:全表扫描(最差)
  • index:全索引扫描
  • range:索引范围查询(between、>、<、in)
  • ref:普通索引等值匹配
  • eq_ref:多表关联,主键 / 唯一索引匹配
  • const:主键 / 唯一索引匹配常量
  • system:单表一行数据(最优)

4. possible_keys & key

  • possible_keys可能使用的索引
  • key实际使用的索引(为 NULL 表示未使用索引)

5. key_len

索引使用的字节数,联合索引中值越大,说明索引利用越充分。计算规则:字段长度 + 字符集字节 + 动态字符串 2 字节 + 允许 NULL1 字节。

6. rows

MySQL 预估需要扫描的行数,值越小越好

7. Extra(★关键额外信息)

  • Using where:服务器层过滤数据,未充分利用索引
  • Using filesort文件排序,性能极差,需优化
  • Using index覆盖索引,无需回表,性能优秀
  • Using index condition:索引下推,减少回表次数
  • Using join buffer:关联查询未使用索引,使用缓存优化

三、海量测试数据构造(50W 员工 + 1W 部门)

优化必须基于真实数据量,通过函数 + 存储过程快速造数:

3.1 创建表

复制代码
-- 部门表
CREATE TABLE dept(
    id INT PRIMARY KEY AUTO_INCREMENT,
    deptName VARCHAR(30),
    address VARCHAR(40),
    ceo INT
);
-- 员工表
CREATE TABLE emp(
    id INT PRIMARY KEY AUTO_INCREMENT,
    empno INT NOT NULL,
    name VARCHAR(20),
    age INT,
    deptId INT
);

3.2 创建随机函数

复制代码
-- 开启函数创建权限
SET GLOBAL log_bin_trust_function_creators=1;

-- 随机字符串
DELIMITER $$
CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
BEGIN
    DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
    DECLARE return_str VARCHAR(255) DEFAULT '';
    DECLARE i INT DEFAULT 0;
    WHILE i < n DO
        SET return_str=CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
        SET i=i+1;
    END WHILE;
    RETURN return_str;
END $$

-- 随机数字
DELIMITER $$
CREATE FUNCTION rand_num(from_num INT,to_num INT) RETURNS INT
BEGIN
    DECLARE i INT DEFAULT 0;
    SET i=FLOOR(from_num+RAND()*(to_num-from_num+1));
    RETURN i;
END $$

3.3 创建存储过程并插入数据

复制代码
-- 插入员工数据(50W条)
DELIMITER $$
CREATE PROCEDURE insert_emp(START INT,max_num INT)
BEGIN
    DECLARE i INT DEFAULT 0;
    SET autocommit=0;
    REPEAT
        SET i=i+1;
        INSERT INTO emp(empno,name,age,deptid) VALUES((START+i),rand_string(6),rand_num(30,50),rand_num(1,10000));
    UNTIL i=max_num END REPEAT;
    COMMIT;
END $$

-- 插入部门数据(1W条)
DELIMITER $$
CREATE PROCEDURE insert_dept(max_num INT)
BEGIN
    DECLARE i INT DEFAULT 0;
    SET autocommit=0;
    REPEAT
        SET i=i+1;
        INSERT INTO dept(deptname,address,ceo) VALUES(rand_string(8),rand_string(10),rand_num(1,500000));
    UNTIL i=max_num END REPEAT;
    COMMIT;
END $$

-- 执行存储过程
CALL insert_dept(10000);
CALL insert_emp(100000,500000);

3.4 辅助工具

复制代码
-- 批量删除表索引
CALL proc_drop_index("库名","表名");
-- 开启SQL执行时间统计
SET profiling=1;
SHOW PROFILES;

四、单表索引失效 10 大场景(★高频面试 + 实战)

建了索引却失效,90% 是踩了这些坑:

1. 索引列上使用计算、函数

复制代码
-- 生效
EXPLAIN SELECT * FROM emp WHERE name LIKE 'abc%';
-- 失效:LEFT函数导致索引失效
EXPLAIN SELECT * FROM emp WHERE LEFT(name,3)='abc';

2. LIKE 以 % 开头(左模糊 / 全模糊)

复制代码
-- 失效
EXPLAIN SELECT * FROM emp WHERE name LIKE '%ab%';

✅ 规范:严禁使用左模糊,需求改用搜索引擎(ES)。

3. 使用!= / <> 不等于

复制代码
-- 失效
EXPLAIN SELECT * FROM emp WHERE name<>'abc';

4. IS NOT NULL 多数场景失效

复制代码
-- 失效
EXPLAIN SELECT * FROM emp WHERE name IS NOT NULL;

特殊:当 NULL 值占比极高时,优化器可能会使用索引。

5. 类型隐式转换

复制代码
-- 字符串列传入数字,索引失效
EXPLAIN SELECT * FROM emp WHERE name=123;

6. 联合索引:违反最左前缀法则

索引idx_age_deptid_name(age,deptid,name)

复制代码
-- 只用到age索引,跳过deptid,name失效
EXPLAIN SELECT * FROM emp WHERE age=30 AND name='abcd';
-- 完全失效:缺少带头大哥age
EXPLAIN SELECT * FROM emp WHERE deptid=1 AND name='abcd';

7. 联合索引:范围条件右边列失效

sql

复制代码
-- 索引 idx_age_deptid_name
-- deptId是范围条件,后面的name索引失效
EXPLAIN SELECT * FROM emp WHERE age=30 AND deptId>1000 AND name='abc';

✅ 优化:范围条件放在联合索引最后。

8. OR 连接非索引列

OR 前后存在未索引列,整体索引失效。

9. 数据区分度极低

重复值极高的列(如性别),建索引无意义。

10. 查询优化器判断全表更快

数据量极小时,优化器会放弃索引,选择全表扫描。

五、多表关联查询优化

核心原则

  1. 索引建在被驱动表的关联字段上
  2. 小表驱动大表,减少循环次数
  3. 关联字段数据类型必须完全一致
  4. 优先使用 JOIN,替代子查询

左外连接优化

复制代码
-- 左连接:class是驱动表,book是被驱动表
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card=book.card;

✅ 关键:给被驱动表 book.card建索引,性能提升最大。

内连接优化

MySQL 优化器会自动选择小表作为驱动表,无需手动指定顺序。

六、子查询优化

严禁使用NOT IN / NOT EXISTS,改用 LEFT JOIN 替代:

复制代码
-- 低效:NOT IN
SELECT * FROM emp WHERE id NOT IN (SELECT ceo FROM dept WHERE ceo IS NOT NULL);

-- 高效:LEFT JOIN + IS NULL
SELECT emp.* FROM emp LEFT JOIN dept ON emp.id=dept.ceo WHERE dept.id IS NULL;

七、排序(ORDER BY)优化

核心目标:消除Using filesort

排序使用索引的三大铁律:

  1. 无过滤,不索引:ORDER BY 必须搭配 WHERE 条件 / LIMIT
  2. 顺序错,不索引:排序顺序必须和联合索引一致
  3. 方向反,不索引:排序字段升降序必须统一

filesort 算法优化

  • 双路排序:两次磁盘 IO,效率低
  • 单路排序:一次磁盘 IO,占用内存大

✅ 优化方案:

  1. 禁止SELECT *,只查询业务需要的字段
  2. 适当调大sort_buffer_size(1M-8M)
  3. 合理设置max_length_for_sort_data

八、分组(GROUP BY)优化

  1. 规则与 ORDER BY 基本一致
  2. GROUP BY无需过滤条件也能使用索引
  3. 分组前 WHERE 过滤结果集尽量≤1000 行

九、覆盖索引优化

✅ 核心:查询的字段都在索引中,无需回表

  • 禁止SELECT *
  • 联合索引覆盖查询字段,减少回表 IO

十、索引优化万能口诀

复制代码
全值匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
like百分写最右,覆盖索引不写*;
不等空值还有or,索引失效要少用;
var引号不能丢,SQL高级也不难;
相关推荐
wjp@0012 小时前
SQL server导出导入数据
运维·服务器·数据库
脑子加油站2 小时前
MySQL8数据库高级特性
数据库·mysql
REDcker3 小时前
OpenSSL:C 语言 TLS 客户端完整示例
c语言·网络·数据库
zly35003 小时前
centos7 mysql 无法被远程连接
数据库·mysql
廿一夏3 小时前
MySql的增删改查
数据库·mysql·dba
瀚高PG实验室3 小时前
HGDB 4.5.8.8开启oracle兼容执行带聚合函数的SQL导致数据库进程被信号11杀死
数据库·sql·oracle·瀚高数据库
炘爚3 小时前
日志系统整体设计步骤以及功能函数梳理
运维·服务器·数据库
_下雨天.3 小时前
PostgreSQL日常维护
数据库·postgresql
神の愛3 小时前
本地连接MySql数据库报错??
数据库·mysql