• 五步 + 十例搞定 SQL 优化万能公式

五步 + 十例搞定 SQL 优化万能公式

2025-05-14 10:00:03 栏目:宝塔面板 98 阅读

在应用开发的早期,数据量少,开发人员开发功能时更重视功能上的实现,随着生产数据的增长,很多SQL语句开始暴露出性能问题,对生产的影响也越来越大,有时可能这些有问题的SQL就是整个系统性能的瓶颈。

一、SQL优化一般步骤

1、通过慢查日志等定位那些执行效率较低的SQL语句

2、explain 分析SQL的执行计划

需要重点关注type、rows、filtered、extra。

type由上至下,效率越来越高。

  • ALL 全表扫描;
  • index 索引全扫描;
  • range 索引范围扫描,常用语<,<=,>=,between,in等操作;
  • ref 使用非唯一索引扫描或唯一索引前缀扫描,返回单条记录,常出现在关联查询中;
  • eq_ref 类似ref,区别在于使用的是唯一索引,使用主键的关联查询;
  • const/system 单条记录,系统会把匹配行中的其他列作为常数处理,如主键或唯一索引查询;
  • null MySQL不访问任何表或索引,直接返回结果;
  • 虽然上至下,效率越来越高,但是根据cost模型,假设有两个索引idx1(a, b, c),idx2(a, c),SQL为"select * from t where a = 1 and b in (1, 2) order by c";如果走idx1,那么是type为range,如果走idx2,那么type是ref;当需要扫描的行数,使用idx2大约是idx1的5倍以上时,会用idx1,否则会用idx2。

Extra

  • Using filesort:MySQL需要额外的一次传递,以找出如何按排序顺序检索行。通过根据联接类型浏览所有行并为所有匹配WHERE子句的行保存排序关键字和行的指针来完成排序。然后关键字被排序,并按排序顺序检索行;
  • Using temporary:使用了临时表保存中间结果,性能特别差,需要重点优化;
  • Using index:表示相应的 select 操作中使用了覆盖索引(Coveing Index),避免访问了表的数据行,效率不错!如果同时出现 using where,意味着无法直接通过索引查找来查询到符合条件的数据;
  • Using index condition:MySQL5.6之后新增的ICP,using index condtion就是使用了ICP(索引下推),在存储引擎层进行数据过滤,而不是在服务层过滤,利用索引现有的数据减少回表的数据。

3、show profile 分析

了解SQL执行的线程的状态及消耗的时间。

默认是关闭的,开启语句“set profiling = 1;”

SHOW PROFILES ;
SHOW PROFILE FOR QUERY  #{id};

4、trace

trace分析优化器如何选择执行计划,通过trace文件能够进一步了解为什么优惠券选择A执行计划而不选择B执行计划。

set optimizer_trace="enabled=on";
set optimizer_trace_max_mem_size=1000000;
select * from information_schema.optimizer_trace;

5、确定问题并采用相应的措施

  • 优化索引;
  • 优化SQL语句:修改SQL、IN 查询分段、时间查询分段、基于上一次数据过滤;
  • 改用其他实现方式:ES、数仓等;
  • 数据碎片处理。

二、场景分析

1、最左匹配

1)索引

KEY `idx_shopid_orderno` (`shop_id`,`order_no`)

2)SQL语句

select * from _t where orderno=''

查询匹配从左往右匹配,要使用order_no走索引,必须查询条件携带shop_id或者索引(shop_id,order_no)调换前后顺序。

2、隐式转换

1)索引

KEY `idx_mobile` (`mobile`)

2)SQL语句

select * from _user where mobile=12345678901

隐式转换相当于在索引上做运算,会让索引失效。mobile是字符类型,使用了数字,应该使用字符串匹配,否则MySQL会用到隐式替换,导致索引失效。

3、大分页

1)索引

KEY `idx_a_b_c` (`a`, `b`, `c`)

2)SQL语句

select * from _t where a = 1 and b = 2 order by c desc limit 10000, 10;

对于大分页的场景,可以优先让产品优化需求,如果没有优化的,有如下两种优化方式:

  • 一种是把上一次的最后一条数据,也即上面的c传过来,然后做“c < xxx”处理,但是这种一般需要改接口协议,并不一定可行;‘
  • 另一种是采用延迟关联的方式进行处理,减少SQL回表,但是要记得索引需要完全覆盖才有效果,SQL改动如下:
select t1.* from _t t1, (select id from _t where a = 1 and b = 2 order by c desc limit 10000, 10) t2 where t1.id = t2.id;

4、in + order by

1)索引

KEY `idx_shopid_status_created` (`shop_id`, `order_status`, `created_at`)

2)SQL语句

select * from _order where shop_id = 1 and order_status in (1, 2, 3) order by created_at desc limit 10

in查询在MySQL底层是通过n*m的方式去搜索,类似union,但是效率比union高。

in查询在进行cost代价计算时(代价 = 元组数 * IO平均值),是通过将in包含的数值,一条条去查询获取元组数的,因此这个计算过程会比较的慢,所以MySQL设置了个临界值(eq_range_index_dive_limit),5.6之后超过这个临界值后该列的cost就不参与计算了。因此会导致执行计划选择不准确。默认是200,即in条件超过了200个数据,会导致in的代价计算存在问题,可能会导致Mysql选择的索引不准确。

3)处理方式

可以(order_status, created_at)互换前后顺序,并且调整SQL为延迟关联。

5、范围查询阻断,后续字段不能走索引

1)索引

KEY `idx_shopid_created_status` (`shop_id`, `created_at`, `order_status`)

2)SQL语句

select * from _order where shop_id = 1 and created_at > '2021-01-01 00:00:00' and order_status = 10

范围查询还有“IN、between”。

6、不等于、不包含不能用到索引的快速搜索

可以用到ICP

select * from _order where shop_id=1 and order_status not in (1,2)
select * from _order where shop_id=1 and order_status != 1

在索引上,避免使用NOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKE等。

7、优化器选择不使用索引的情况

如果要求访问的数据量很小,则优化器还是会选择辅助索引,但是当访问的数据占整个表中数据的蛮大一部分时(一般是20%左右),优化器会选择通过聚集索引来查找数据。

select * from _order where  order_status = 1

查询出所有未支付的订单,一般这种订单是很少的,即使建了索引,也没法使用索引。

8、复杂查询

select sum(amt) from _t where a = 1 and b in (1, 2, 3) and c > '2020-01-01';
select * from _t where a = 1 and b in (1, 2, 3) and c > '2020-01-01' limit 10;

如果是统计某些数据,可能改用数仓进行解决;

如果是业务上就有那么复杂的查询,可能就不建议继续走SQL了,而是采用其他的方式进行解决,比如使用ES等进行解决。

9、asc和desc混用

select * from _t where a=1 order by b desc, c asc

desc 和asc混用时会导致索引失效。

10、大数据

对于推送业务的数据存储,可能数据量会很大,如果在方案的选择上,最终选择存储在MySQL上,并且做7天等有效期的保存。

那么需要注意,频繁的清理数据,会造成数据碎片,需要联系DBA进行数据碎片处理。

本文地址:https://www.yitenyun.com/208.html

搜索文章

Tags

数据库 API FastAPI Calcite 电商系统 MySQL Web 应用 异步数据库 数据同步 ACK 双主架构 循环复制 TIME_WAIT 运维 负载均衡 JumpServer SSL 堡垒机 跳板机 HTTPS HexHub Docker 服务器 管理口 服务器性能 JumpServer安装 堡垒机安装 Linux安装JumpServer Deepseek 宝塔面板 Linux宝塔 生命周期 esxi esxi6 root密码不对 无法登录 web无法登录 SQL 查询 序列 核心机制 Windows Windows server net3.5 .NET 安装出错 HTTPS加密 Windows宝塔 Mysql重置密码 开源 PostgreSQL 存储引擎 锁机制 宝塔面板打不开 宝塔面板无法访问 查看硬件 Linux查看硬件 Linux查看CPU Linux查看内存 行业 趋势 Oracle 处理机制 无法访问宝塔面板 Undo Log 机制 Spring Redis 异步化 监控 连接控制 InnoDB 数据库锁 机器学习 优化 万能公式 动态查询 Serverless 无服务器 语言 ES 协同 响应模型 group by 索引 技术 分页查询 openHalo scp Linux的scp怎么用 scp上传 scp下载 scp命令 缓存方案 缓存架构 缓存穿透 Postgres OTel Iceberg 工具 存储 高可用 GreatSQL 连接数 数据 主库 SVM Embedding 日志文件 MIXED 3 R edis 线程 国产数据库 Linux 安全 R2DBC Netstat Linux 服务器 端口 SQLite-Web SQLite 数据库管理工具 加密 场景 启动故障 ​Redis 推荐模型 自定义序列化 防火墙 黑客 云原生 Recursive SQLark RocketMQ 长轮询 配置 共享锁 AI 助手 向量数据库 大模型 OB 单机版 Hash 字段 PG DBA Rsync Ftp 信息化 智能运维 不宕机 磁盘架构 电商 系统 架构 向量库 Milvus 数据分类 Python 修改DNS Centos7如何修改DNS 业务 IT运维 Canal filelock 流量 • 索引 • 数据库 传统数据库 向量化 线上 库存 预扣 分库 分表 sftp 服务器 参数 PostGIS mini-redis INCR指令 MVCC 人工智能 推荐系统 语句 聚簇 非聚簇 MySQL 9.3 频繁 Codis redo log 重做日志 同城 双活 Doris SeaTunnel MongoDB MCP 开放协议 失效 缓存 Redisson 锁芯 高效统计 今天这篇文章就跟大家 数据类型 虚拟服务器 虚拟机 内存 工具链 事务 Java 开发 INSERT COMPACT 主从复制 代理 prometheus Alert 数据备份 千万级 大表 窗口 函数 容器 数据结构 ZODB 发件箱模式 SSH 聚簇索引 非聚簇索引 网络架构 网络配置 EasyExcel MySQL8 引擎 性能 分布式架构 分布式锁​ QPS 高并发 崖山 新版本 Web B+Tree ID 字段 RDB AOF 数据脱敏 加密算法 分布式 集中式 分页 核心架构 订阅机制 Go 数据库迁移 Web 接口 OAuth2 Token Redis 8.0 速度 服务器中毒 播客 模型 数据页 数据集成工具 读写 自动重启 网络故障 容器化 DBMS 管理系统 StarRocks 数据仓库 SpringAI 排行榜 排序 池化技术 连接池 微软 SQL Server AI功能 Redka JOIN MGR 分布式集群 Caffeine CP 部署 原子性 Entity 网络 LRU 业务场景 事务隔离 Pottery Valkey Valkey8.0 Testcloud 云端自动化 dbt 数据转换工具 分页方案 排版 数据字典 兼容性 1 ReadView 优化器 事务同步 sqlmock 意向锁 记录锁 悲观锁 乐观锁 关系数据库 日志 Weaviate 对象 单点故障 单线程 UUIDv7 主键 仪表盘 InfluxDB Order AIOPS UUID ID 编程 RAG HelixDB Ansible Pump Crash 代码 IT 双引擎 分布式锁 Zookeeper 产业链 恢复数据 字典 订单 LLM 线程安全 List 类型 国产 用户 慢SQL优化 表空间 拦截器 动态代理 解锁 调优 Next-Key RR 互联网 GitHub Git 快照读 当前读 视图 矢量存储 数据库类型 AI代理 神经系统 查询规划 count(*) count(主键) 行数 CAS 算法 技巧 多线程 并发控制 恢复机制 闪回