• 如何优化一条SQL语句的性能?

如何优化一条SQL语句的性能?

2025-05-12 10:00:03 栏目:宝塔面板 28 阅读

前言

"苏工,订单列表又崩了!"

接到电话时,我对着监控大屏上999ms的SQL响应时间哭笑不得。

几年来,我发现一个定律:所有SQL问题都是在凌晨三点爆发!

今天抽丝剥茧,教你用架构师的思维给慢SQL开刀手术。

希望对你会有所帮助。

1.术前检查:找准病灶

(1)EXPLAIN 查看执行计划

使用EXPLAIN查看SQL语句的执行计划,相当于给SQL拍了张X光。

下面是一个典型的SQL问题,它是某电商平台历史订单查询的SQL语句:

SELECT * 
FROM orders o 
LEFTJOINusers u ON o.user_id = u.id
LEFTJOIN products p ON o.product_id = p.id
WHERE o.create_time > '2023-01-01'
AND u.vip_level > 3
AND p.category_id IN (5,8)
ORDERBY o.amount DESC
LIMIT1000,20;

使用EXPLAIN关键字查看执行计划的结果如下:

+----+-------------+-------+------+---------------+------+---------+------+---------+---------------------------------+
| id | select_type | table | type | possible_keys | key  | rows    | Extra| key_len | 
+----+-------------+-------+------+---------------+------+---------+------+---------+---------------------------------+
| 1  | SIMPLE      | o     | ALL  | idx_user_time | NULL | 1987400 | Using where; Using filesort     |  
| 1  | SIMPLE      | u     | ALL  | PRIMARY       | NULL | 100000  | Using where                     |
| 1  | SIMPLE      | p     | ALL  | PRIMARY       | NULL | 50000   | Using where                     |
+----+-------------+-------+------+---------------+------+---------+------+---------+---------------------------------+

诊断报告:

  • 全表扫描三连击(type=ALL)
  • filesort暴力排序(内存警告)
  • 索引全军覆没

2.手术方案:精准打击

(1)单表代谢手术

如果通过执行计划查到是索引有问题,我们就需要单独优化索引。

病根:JSON字段索引失效

错误用法:

ALTER TABLE users ADD INDEX idx_extend ((extend_info->'$.is_vip')); 

extend_info字段是JSON类型的字段,即使创建了索引,索引也会丢失。

正解姿势(MySQL 8.0+):

ALTER TABLE users ADD INDEX idx_vip_level (vip_level);
ALTER TABLE orders ADD INDEX idx_create_user (create_time, user_id) COMMENT '组合索引覆盖查询';

创建组合索引覆盖查询。

2.2 血管疏通术

卡点分析

原始join顺序是:

orders → users → products

优化后的方案:

(子查询过滤users) → products → orders

调整执行顺序,用小表驱动大表。

重写后的SQL:

SELECT o.* 
FROM products p 
INNERJOIN (
SELECT o.id, o.amount, o.create_time 
FROM orders o 
WHERE o.create_time > '2023-01-01'
) o ON p.id = o.product_id 
INNERJOIN (
SELECTid
FROMusers
WHERE vip_level > 3
) u ON o.user_id = u.id  
WHERE p.category_id IN (5,8)
ORDERBY o.amount DESC
LIMIT1000,20;

术后效果:

  • 先扫小表(users过滤后只有100条)
  • 消除冗余字段传输
  • 减少Join时临时表生成

(3)开颅手术

通过执行计划锁定了问题,走错索引了,该怎么处理呢?

可以通过FORCE INDEX强制指定索引:

SELECT /*+ INDEX(o idx_create_user) */ 
       o.id, o.amount 
FROM orders o FORCE INDEX (idx_create_user)
WHERE o.create_time > '2023-01-01';

使用衍生表加速:

SELECT *
FROM (
SELECTid, amount 
FROM orders 
WHERE create_time > '2023-01-01'
ORDERBY amount DESC
LIMIT1020
) tmp 
ORDERBY amount DESC
LIMIT1000,20;

医嘱:

  • 警惕OR导致的索引失效
  • 用覆盖索引避免回表查询
  • CTE表达式谨慎使用

(4)生命体征监测

查看索引使用:

SHOW INDEX FROM orders;

监控索引使用率:

SELECT object_schema, object_name, index_name,
       count_read, count_fetch 
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE index_name IS NOT NULL;

3.术后护理:体系化治理

(1)SQL消毒中心

需要制定优秀的代码规范,否则可能会出现全表扫描的问题。

在日常工作中,我们要尽可能减少Java代码感染源

MyBatis危险写法:

@Select("SELECT * FROM orders WHERE #{condition}")
List findByCondition(@Param("condition") String condition);

condition参数可以传入任何内容,如何传入了1=1,可能会导致查询所有的数据,走全表扫描,让查询效率变得非常低。

正确做法(参数化查询):

@Select("SELECT * FROM orders WHERE create_time > #{time}")
List findByTime(@Param("time") Date time);

消毒方案

  • SQL审核平台接入(如Yearning)
  • MyBatis拦截器拦截全表更新
  • 自动化EXPLAIN分析流水线

(2)查杀大表癌症

如果遇到大表的癌症病例,可以用分库分表的方案解决。

病历案例:3亿订单表终极解决方案

// Sharding-JDBC分片配置
spring.shardingsphere.rules.sharding.tables.orders.actual-data-nodes=ds$0..1.orders_$->{2020..2023}
spring.shardingsphere.rules.sharding.tables.orders.table-strategy.standard.sharding-column=create_time
spring.shardingsphere.rules.sharding.tables.orders.table-strategy.standard.sharding-algorithm-name=time_range

化疗方案:

  • 时间维度分片(2020~2023年度表)
  • 用户ID取模分库
  • 冷热分离(OSS归档历史数据

医嘱总结

优化三板斧:

  • 定位:慢查询日志+执行计划分析
  • 切割:化繁为简拆分多步执行
  • 重建:符合业务场景的数据结构

避坑口诀:

  • 索引不是银弹,覆盖才是王道
  • Join水深,能拆就拆
  • Order By+Limit≠分页优化

最后送上苏三的传秘方:当你优化SQL到怀疑人生时,不妨试试这三味药:

  • 删业务逻辑
  • 加缓存
  • 换数据库

保证药到病除(老板打不打死你我就不管了,哈哈哈)!

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

搜索文章

Tags

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