MySQL索引优化实战:高效查询的黄金法则

索引

索引设计

什么叫做"加对了索引"?

建对索引 = 只给【经常用来查询、筛选、排序、关联】的字段建索引,让 MySQL 一查就能命中,不走全表扫描

1.给高频率查询字段加索引

在文章表中:

KEY idx_category_id (category_id)

KEY idx_url_slug (url_slug)

按照文章分类 / 别名查询是"where="高频操作可以建立索引

2.给排序/分组字段加索引

在文章表中:

KEY idx_create_time (create_time)

KEY idx_update_time (update_time)

文章列表按发布时间 / 更新时间倒序排序是高频展示操作,给排序字段建索引避免文件排序,提升查询速度

3.给连表查询的关联字段加索引

在文章表中:

KEY idx_user_id (user_id)

在评论表中:

KEY idx_article_id (article_id)

文章关联用户信息、评论关联文章内容是常用连表操作,给关联字段建索引,避免连表时全表扫描

什么叫做"加错了索引"?

区分度极低的字段建索引比如 status 只有 0/1 两种值,建了索引 MySQL 也不会用,白占空间。

从来不查的字段建索引比如文章的 create_ip 从来不用来查询,却建了索引。

滥建索引一个表建 10+ 个索引,写入数据变得极慢(增删改都要更新所有索引)。

频繁更新的字段:索引需要维护 B+Tree 的有序性,频繁 UPDATE 索引列代价大。

数据量很小的表(几百行):全表扫描比走索引还快,建了也没意义。

联合索引遵循最左前缀

建立联合索引的时候遵循最左前缀原则,假如联合索引是a,b,c,那么查询a,查询a,b,查询a,b,c都会走索引优化

技巧:联合索引要遵循最左前缀原则,索引 (a,b,c) 只能命中 a、ab、abc 三种连续查询。设计时把高频字段放左边,查询条件顺序不影响索引命中,MySQL 会自动优化;范围查询会截断后面的索引列,如果只查询索引字段,还能实现覆盖索引不回表。

EXPLAIN 执行计划

分析慢 SQL 会先通过慢查询日志定位,然后用 EXPLAIN 查看执行计划:重点看 type 避免全表扫描,key 确认索引命中,rows 保证扫描行数少,Extra 排查文件排序和临时表问题,最后针对性优化

使用方式也非常简单,直接在SQL语句前加上 explain 关键字就可以了。

列名 示例值 说明
type ref 访问类型:关键性能指标,常见类型: - system/const:唯一值匹配(性能最佳) - eq_ref:主键 / 唯一索引连接 - ref:非唯一索引匹配 - range:索引范围扫描 - index:全索引扫描 - ALL:全表扫描(需优化)
key idx_user_id 实际选择的索引。若为 NULL,表示未使用索引。
rows 50 预估扫描行数。数值越小越好,若与实际差距大,可能统计信息过期(需 ANALYZE TABLE)。
Extra Using where 附加信息: - Using index:覆盖索引(无需回表) - Using temporary:使用临时表 - Using filesort:文件排序

SQL写法

避免索引失效

不用左模糊

SELECT * FROM article WHERE title LIKE '%Java';左模糊,索引失效

SELECT * FROM article WHERE title LIKE 'Java%';右模糊,索引生效

不对索引字段做运算/函数

-- 给索引字段 create_time 用了DATE()函数,索引失效

SELECT * FROM article WHERE DATE(create_time) = '2026-05-29';

-- 直接对create_time做范围判断,索引正常生效

SELECT * FROM article WHERE create_time BETWEEN '2026-05-29 00:00:00' AND '2026-05-29 23:59:59';

不用select*

-- 列表页只需要id、标题、发布时间、作者ID,不查大文本content

SELECT id, title, create_time, user_id FROM article WHERE category_id = 1;

小表驱动大表

大表作为被驱动表对应字段一定要有索引,不然大表会全表扫描,小表的对应字段索引可以有,影响微乎其微!!!

-- 大表article在前,每次循环都要查user表,效率低

SELECT * FROM article a JOIN user u ON a.user_id = u.id;

-- 小表user在前,先查用户,再驱动查文章(文章表user_id有索引,查询很快)

SELECT * FROM user u JOIN article a ON u.id = a.user_id;

优化深分页

深分页优化不是因为 LIMIT 不走索引,而是大偏移量会导致 MySQL 扫描并丢弃大量数据。优化方式是通过主键 ID 直接定位起始位置,避免偏移量带来的性能损耗

--坏写法(LIMIT 10000,10,扫描前 10000 条再丢弃,极慢)

SELECT * FROM article ORDER BY create_time DESC LIMIT 10000, 10;

-- 已知上一页最后一条文章的ID是10000,直接查比它ID小的10条数据

SELECT * FROM article WHERE id < 10000 ORDER BY create_time DESC LIMIT 10;

表结构设计

字段用最小类型

比如文章状态用 tinyint 代替 varchar,阅读量用 int 代替 bigint,减少存储占用,提升索引效率

拆分大文本字段

把文章正文这类不常用的大文本单独拆成附表,主表只存列表页需要的轻量字段,降低 IO 开销,避免拖慢主表查询

避免字段为 NULL

给所有字段设 NOT NULL 和默认值,规避 NULL 带来的问题:null在唯一索引中会出现null != null,容易数据重复。应用程序处理null容易出错,不如''。聚合函数count具体字段会忽视null,count * 又会算上null,容易数据不一致。

数据库配置

调大缓存池(innodb_buffer_pool_size

缓存池是 InnoDB 专门用来存常用索引和数据的内存区域,就像你常用的文件放在桌面,打开直接秒开,不用去硬盘里找。缓存池越大,能留在内存里的数据越多,磁盘读取越少,速度越快

配置方式(my.cnf):

建议设为服务器物理内存的50%-70%(比如16G内存的机器设为8G)

innodb_buffer_pool_size = 8G

使用 InnoDB 引擎

InnoDB 是 MySQL 默认的存储引擎,比老的 MyISAM 多了「事务支持、行级锁、聚簇索引」三个核心能力,是高并发项目的标配。

配置方式

建表时默认就是 InnoDB,也可以手动指定:

CREATE TABLE article (

id INT PRIMARY KEY AUTO_INCREMENT,

title VARCHAR(100) NOT NULL DEFAULT ''

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

开启慢查询日志

慢查询日志会记录所有执行时间超过你设置的阈值的 SQL 语句,帮你精准找到 "拖慢系统的元凶",而不是盲猜哪里慢。

配置方式(my.cnf):

开启慢查询日志

slow_query_log = 1

日志文件存储路径

slow_query_log_file = /var/log/mysql/slow.log

慢查询阈值:超过2秒的SQL会被记录

long_query_time = 2

额外记录没使用索引的SQL(方便发现索引问题)

log_queries_not_using_indexes = 1

可以用mysqldumpslow工具分析日志,比如:

按执行时间排序,查看最耗时的SQL

mysqldumpslow -s t /var/log/mysql/slow.log

业务层兜底

热点数据加 Redis 缓存降低 DB 压力,主从架构做读写分离分摊读流量,用 MQ 异步化处理非实时操作,提升主流程响应速度

相关推荐
TDengine (老段)1 小时前
TDengine Commit 与 Flush 机制 — 从内存到磁盘的数据落盘全流程
大数据·数据库·物联网·架构·时序数据库·iot·tdengine
Dxy12393102162 小时前
Python 操作 MySQL 事务:从入门到避坑
android·python·mysql
ID_180079054732 小时前
(淘宝 / 京东)商品评论 API 接口:技术实战案例与架构分析
服务器·数据库·架构
爱莉希雅&&&2 小时前
Zabbix监控初步搭建
linux·运维·数据库·mysql·zabbix
狼与自由2 小时前
mysql到clickhouse
数据库·mysql·clickhouse
六月雨滴2 小时前
Oracle 数据库之归档日志
数据库·oracle·dba
土狗TuGou2 小时前
SQL内功笔记 · 第6篇:窗口函数的使用ROW_NUMBER等
java·数据库·后端·sql·mysql
川石课堂软件测试2 小时前
使用mock进行接口测试教程
数据库·python·功能测试·测试工具·华为·单元测试·appium
Solis程序员2 小时前
MongoDB 超全入门到实战:从原理、CRUD到高可用架构
数据库·mongodb·架构