当前位置: 首页 > news >正文

【Mysql】SQL优化最佳实践

可以把“慢 SQL 优化”理解成一个固定套路:

先找到慢 SQL → 再判断慢在哪里 → 最后针对原因优化。

你这份总结内容比较全,我帮你按面试/学习逻辑重新解释一遍。


一、慢 SQL 怎么定位?

首先你得知道:哪条 SQL 慢。

常用方式是开启慢查询日志

慢查询日志会记录执行时间超过阈值的 SQL,比如超过 1 秒、2 秒、5 秒的 SQL。

例如:

select*fromorderwhereuser_id=1001;

如果它执行了 5 秒,就可能被记录到慢查询日志里。

定位到慢 SQL 后,下一步不是马上加索引,而是先分析它为什么慢。


二、慢 SQL 为什么会慢?

慢 SQL 的原因通常分几类。

1. 数据量太大

比如一张订单表有几千万数据,你查:

select*fromorderwherestatus=1;

如果status = 1的数据非常多,即使有索引,也可能很慢。

因为数据库要扫描很多行、返回很多数据。

这类问题可能需要:

加索引 分页 分库分表 冷热数据分离 归档历史数据

2. 并发量太大

有时候 SQL 本身不算特别慢,但同时有几百、几千个请求都在查数据库,数据库扛不住。

比如秒杀、首页推荐、热门商品详情页。

这类问题不一定只靠 SQL 优化解决,还需要:

缓存 读写分离 限流 异步处理 数据库扩容

3. 索引没用上,或者用得不好

这是慢 SQL 最常见的原因。

比如表中有 100 万条数据:

select*fromuserwherephone='13000000001';

如果phone没有索引,就可能全表扫描。

也有些情况是建了索引但没用上,比如:

select*fromuserwhereleft(phone,3)='130';

对索引字段使用函数,可能导致索引失效。


4. SQL 写法不合理

比如:

select*fromuser;

不管业务是否需要所有字段,都全部查出来,会增加:

解析成本 网络传输成本 内存消耗 回表成本

再比如深分页:

select*fromorderorderbyidlimit1000000,10;

数据库要先扫描前 1000000 条,然后丢掉,只返回 10 条,所以很慢。


5. 表结构设计不合理

比如一个用户表设计成这样:

id name phone avatar_blob resume_text login_time ...

如果里面有很多大字段,比如头像二进制、长文本,普通查询用户信息时也可能受到影响。

这时可以把大字段拆出去:

user 基础表 user_ext 扩展表

常用字段放主表,不常用字段放扩展表。


6. 业务设计不合理

有些慢 SQL 不是数据库的问题,而是需求本身不合理。

比如用户非要查第 100000 页:

select*fromorderlimit999990,10;

这种深分页本来就很慢。

又比如日报、月报、统计报表,每次页面打开都实时统计:

selectcount(*),sum(amount)fromorderwheredate='2026-05-26';

如果数据量很大,实时算就会很慢。

更合理的方式是:提前在低峰期算好结果,页面直接查统计结果。


三、索引优化怎么做?

索引优化是慢 SQL 优化里最核心的一部分。

1. 不是索引越多越好

索引可以提高查询速度,但也有代价。

因为每次插入、更新、删除数据时,不仅要改表数据,还要维护索引树。

比如你有一张表:

user 表

上面建了 10 个索引,那么插入一条用户数据时,MySQL 可能要维护 10 棵索引树。

所以索引多了会影响:

插入性能 更新性能 删除性能 磁盘空间 优化器选择执行计划的成本

因此一般建议:单表索引不要太多,常见建议控制在 5 个左右。

这个不是绝对规则,而是经验值。


2. 联合索引怎么建?

联合索引不是随便把几个字段拼起来,而是要考虑查询条件。

例如 SQL:

SELECTage,city,nameFROMuserWHEREage=20ANDcity='Beijing'ORDERBYname;

可以建立联合索引:

(age,city,name)

为什么?

因为查询过程可以理解成:

先用age = 20定位一批数据,再用city = 'Beijing'缩小范围,最后数据在索引中已经按name排好序。

这样可以减少:

扫描行数 回表次数 额外排序 filesort

3. 联合索引字段顺序怎么放?

你总结里写了两个原则:

区分度高的放左边 使用频繁的放左边

这两个都对,但要补充一点:要结合具体 SQL 来看。

比如联合索引:

(a,b,c)

可以命中:

wherea=1wherea=1andb=2wherea=1andb=2andc=3

但是下面这种通常不能很好利用这个联合索引:

whereb=2wherec=3

因为不满足最左前缀原则。

所以建联合索引时,优先看:

where 条件 order by 条件 group by 条件 字段区分度 字段使用频率

四、SQL 语句优化

1. 尽量不要使用 select *

例如:

select*fromuserwhereid=1;

如果只需要名字和手机号,应该写成:

selectname,phonefromuserwhereid=1;

原因是:

第一,select *会查出很多无用字段。

第二,字段越多,网络传输越大。

第三,如果查询字段都在索引里,可以走覆盖索引;但select *往往需要回表。

例如有索引:

(name,phone)

SQL:

selectname,phonefromuserwherename='Tom';

可能直接从索引里拿到数据,不用回表。

但如果:

select*fromuserwherename='Tom';

就可能需要根据索引找到主键,再回到聚簇索引中查完整数据。


2. join 时,小表驱动大表

比如:

A 表:1000 行 B 表:100 万行

如果用 A 表驱动 B 表,大概是:

遍历 A 表 1000 行 每一行去 B 表查匹配数据

如果 B 表相关字段有索引,效率比较高。

但如果反过来,用 B 表驱动 A 表:

遍历 B 表 100 万行 每一行去 A 表查匹配数据

成本就大很多。

所以一般说:小表驱动大表。

不过严格来说,MySQL 优化器会根据统计信息自动选择驱动表,我们写 SQL 时要尽量让连接条件、索引设计更有利于优化器选择好方案。


3. union all 优先于 union

区别是:

union

会合并结果并去重。

unionall

只是简单合并,不去重。

例如:

selectnamefromuser_aunionselectnamefromuser_b;

MySQL 要额外做去重,可能涉及排序或临时表。

如果业务允许重复,应该用:

selectnamefromuser_aunionallselectnamefromuser_b;

效率更高。


五、表结构优化

1. 常一起查的字段放一张表

如果业务中经常同时查:

用户姓名 手机号 头像地址 注册时间

这些可以放在一张用户基础表里,避免频繁 join。

但如果有些字段很少查,比如:

个人简介 证件照片 详细备注 扩展配置

可以拆到扩展表里。


2. 适当反范式设计

数据库三范式强调减少冗余。

但是在高并发业务里,有时为了性能,会保留一些冗余字段。

例如订单表里保存:

user_id user_name

虽然user_name可以通过用户表查到,但为了避免订单查询时频繁 join 用户表,可以冗余一份用户名。

这就是反范式设计。

它的代价是:数据一致性维护更复杂。

比如用户改名后,订单表里的user_name是否要同步更新?这要根据业务决定。


3. 选择合适的数据类型

字段类型越小,存储越省,索引也越小,查询通常越快。

比如年龄字段:

ageint

其实没必要,使用:

agetinyint

就够了。

再比如状态字段:

statusvarchar(20)

如果状态只有 0、1、2,可以用:

statustinyint

这样更节省空间。


4. 避免 TEXT、BLOB 大字段

大字段会让表变得很“重”。

比如图片、文件、长文本,不建议直接存在数据库表里。

更常见的做法是:

文件放 OSS / 文件服务器 数据库只存文件 URL

如果必须使用大字段,也建议拆到扩展表里。


5. 尽量 NOT NULL

如果字段允许 NULL,MySQL 需要额外记录 NULL 标志位。

另外 NULL 在比较、计算、索引统计时也会更复杂。

例如:

selectavg(score)fromstudent;

如果score有 NULL,MySQL 要特殊处理。

所以除非业务上确实需要表达“未知”或“不存在”,否则可以尽量设置为:

notnull

并给默认值。


6. 不要用字符串存日期

不要这样设计:

create_timevarchar(20)

更推荐:

create_timedatetime

或者:

create_timetimestamp

因为字符串日期有几个问题:

占用空间更大 比较效率低 容易格式不统一 不能方便使用日期函数 索引效果可能更差

比如日期类型可以直接写:

wherecreate_time>='2026-05-01'

也可以用日期函数做统计。


7. 单表字段不要太多

如果一张表有几十个、上百个字段,每次查询时行数据很大,会影响 IO 和缓存效率。

可以做冷热分离:

热字段:经常查,经常用 冷字段:很少查,不常用

例如:

user 表:id、name、phone、status、create_time user_ext 表:address、intro、remark、extra_config

六、业务优化

业务优化其实很重要,因为有些慢 SQL 靠加索引也救不了。

1. 深分页优化

普通分页:

select*fromorderorderbyidlimit1000000,10;

很慢。

可以改成基于上一页最后一个 id 查询:

select*fromorderwhereid>1000000orderbyidlimit10;

这种叫游标分页,性能更好。


2. 报表提前计算

比如 T+1 报表,意思是统计前一天的数据。

这种没必要用户打开页面时实时算。

可以每天凌晨定时任务算好,把结果放到报表表中:

order_daily_report

页面打开时直接查结果表。


七、架构优化

当单纯 SQL 和索引优化不够时,就要考虑架构层面的优化。

1. 读写分离

主库负责写:

insert update delete

从库负责读:

select

适合读多写少的业务。

比如电商商品详情页,大量用户都在读商品信息,可以走从库。


2. 分库分表

当单表数据量非常大,比如订单表上亿数据,就可以考虑分库分表。

例如按用户 ID 分表:

order_0 order_1 order_2 ...

优点是降低单表数据量。

缺点是复杂度明显增加:

跨表查询复杂 分页复杂 事务复杂 运维复杂 数据迁移复杂

所以分库分表不是第一选择,通常是数据量确实很大时才考虑。


3. 缓存机制

对于热点数据,可以放到 Redis 中。

比如:

商品详情 首页推荐 用户权限信息 字典配置

请求先查 Redis,Redis 没有再查数据库。

这样可以明显减少数据库压力。

不过缓存也有问题:

缓存一致性 缓存击穿 缓存穿透 缓存雪崩

所以也要设计好过期时间、互斥锁、布隆过滤器等机制。


4. 分布式数据库

例如 TiDB 这类数据库,可以支持更大的数据规模和更高并发。

但它不是所有场景都适合,通常用于:

数据量大 并发高 需要水平扩展 传统单机 MySQL 不够用

八、面试时可以这样回答

如果面试官问:你是怎么优化慢 SQL 的?

可以这样说:

我一般会先通过慢查询日志或者监控系统定位慢 SQL,然后用 EXPLAIN 分析执行计划,重点看 type、key、rows、Extra 等字段,判断是否走了索引、扫描行数是否过大、是否出现 filesort 或 temporary。

如果是索引问题,就根据 where、order by、group by 设计合适的索引,尤其是联合索引,注意最左前缀原则和字段区分度。

如果是 SQL 写法问题,就避免 select *、优化 join、用 union all 替代 union、避免深分页。

如果是表结构问题,就考虑字段类型优化、大字段拆分、冷热分离、适当反范式。

如果是业务或架构问题,就考虑缓存、读写分离、提前计算报表、分库分表等方案。

总的来说,慢 SQL 优化不是单纯加索引,而是从 SQL、索引、表结构、业务和架构多个层面综合分析。


你这份总结的核心可以记成一句话:

慢 SQL 优化的本质,就是减少扫描数据量、减少回表、减少排序、减少 join、减少数据库压力。

http://www.cnnetsun.cn/news/2672288.html

相关文章:

  • 别再死记硬背公式了!用Python可视化带你直观理解两个高斯分布相乘(附Matlab/NumPy代码)
  • 告别绿屏!Win11重装或升级到11代CPU,这个RAID驱动你必须提前准备好
  • 如何将QQ音乐加密文件转换为通用音频格式:qmc-decoder完全指南
  • Obsidian PDF++:重新定义你的PDF知识管理方式
  • 完整QQ音乐音频解密教程:qmcdump让你的加密音乐文件重获自由播放能力
  • 从裸机到RTOS:手把手教你为正点原子Nano STM32F103移植RT-Thread Nano内核(MDK5环境)
  • 3分钟快速移除Windows Defender终极指南:告别烦人弹窗和性能占用
  • 收藏 | 产品经理必看:从功能设计到任务设计,掌握大模型时代的产品开发新范式
  • Windows Cleaner终极指南:3个简单步骤让你的电脑告别卡顿和空间不足
  • 2026 零基础网络安全学习路线:从入门到上岗,保姆级实战教程
  • 从零构建AI日程管家:基于GPT-4与自动化工具的个人效率系统实践
  • 72.跨版本刷机原理全解|Android10-14/iOS16-18 Bootloader与DFU底层机制
  • 别再只填频率和位宽了!ZYNQ MPSoC DDR4配置中那些容易被忽略的‘小参数’详解
  • 大数据如何重塑医疗、法律、零售三大传统行业:从技术原理到实战落地
  • 飞书机器人集成 OpenClaw 智能电脑控制实战
  • 如何告别网盘下载限速?三分钟掌握高效文件获取方案
  • Beyond Compare 5授权密钥生成完整指南:三步实现专业文件对比工具永久激活方案
  • 抖音批量下载工具深度解析:如何高效获取无水印内容
  • 怎样快速搭建个人抖音视频解析服务:完整实战指南
  • 收藏了很多机器视觉知识,为什么一做项目就卡住?
  • 终极NCM文件解密指南:ncmdumpGUI图形界面工具完整使用教程
  • 手把手教你用xdisp_virt在Windows上接收iPhone投屏,还能反向控制(附蓝牙驱动配置)
  • AI聊天机器人实战:从零构建驱动业务增长的智能对话系统
  • 手把手教你用信号源和示波器DIY一个简易TDR,实测同轴电缆阻抗(附避坑指南)
  • 从‘打包后’到‘真机上’:Unity Profiler移动端性能调优全流程实录
  • STM32F103C8T6 全参数深度解析
  • 别再死记硬背了!用这5个高频Docker命令场景,帮你彻底搞懂容器操作
  • ADS Momentum RF仿真不准?试试把Compression Level改成Reduced(附默认设置建议)
  • YOLOv8论文党必备:如何科学设计并自动化执行你的消融实验?
  • 康威尔生命游戏理论基础分析