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

SQL优化详解与案例、以及索引失效场景;

在这里插入图片描述

SQL优化详解与案例

(1)避免使用 *

案例对比

-- ❌ 不推荐
SELECT * FROM users WHERE id = 1;-- ✅ 推荐
SELECT id, username, email, created_at FROM users WHERE id = 1;

原因分析

  • 网络传输开销* 会查询所有列,包括不需要的大字段(如 TEXT、BLOB),增加网络IO
  • 无法使用覆盖索引:如果只查询索引列,可以直接从索引获取数据,避免回表
  • 内存消耗:查询更多数据占用更多内存缓冲区
  • 维护性差:表结构变更时,应用程序可能获取到不需要的新列

(2)合理创建索引

案例

-- 表结构
CREATE TABLE orders (id INT PRIMARY KEY,user_id INT,status VARCHAR(20),created_at DATETIME,amount DECIMAL(10,2)
);-- ✅ 为高频查询字段创建索引
CREATE INDEX idx_user_status ON orders(user_id, status);
CREATE INDEX idx_created_at ON orders(created_at);-- 高效查询
SELECT * FROM orders WHERE user_id = 100 AND status = 'paid';

原因分析

  • 减少扫描行数:索引是排序的数据结构(B+树),可以快速定位数据
  • 避免全表扫描:无索引时需要逐行扫描整张表
  • 注意:过多索引会影响写入性能(INSERT/UPDATE/DELETE需要维护索引)

(3)避免 WHERE 中的 NULL 值判断

案例对比

-- ❌ 不推荐(全表扫描)
SELECT * FROM users WHERE age IS NULL;-- ✅ 推荐:设计时避免 NULL,使用默认值
ALTER TABLE users MODIFY COLUMN age INT NOT NULL DEFAULT 0;
SELECT * FROM users WHERE age = 0;

原因分析

  • 索引失效:大多数数据库的索引不存储 NULL 值,导致 IS NULLIS NOT NULL 无法使用索引

  • 解决方案

    • 设计表时使用 NOT NULL + 默认值
    • 如果必须用 NULL,可以为该列创建特殊索引(部分数据库支持)

(4)避免使用 OR,用 IN 替换

案例对比

-- ❌ 不推荐(可能全表扫描)
SELECT * FROM products WHERE category_id = 1 OR category_id = 2 OR category_id = 3;-- ✅ 推荐
SELECT * FROM products WHERE category_id IN (1, 2, 3);

原因分析

  • OR 导致索引失效:多个 OR 条件可能导致优化器放弃索引,改为全表扫描
  • IN 可以使用索引:优化器会将 IN 转换为多个等值查询,能利用索引
  • 特殊情况:如果 OR 连接的是不同列,改用 UNION ALL
-- 不同列的 OR
SELECT * FROM users WHERE username = 'admin' 
UNION ALL 
SELECT * FROM users WHERE email = 'admin@example.com';

(5)LIKE 不以 % 开头

案例对比

-- ❌ 不推荐(全表扫描)
SELECT * FROM products WHERE name LIKE '%手机%';-- ✅ 推荐(可以使用索引)
SELECT * FROM products WHERE name LIKE '苹果%';

原因分析

  • 前缀匹配可用索引:索引是按字母顺序排列的,'苹果%' 可以快速定位到"苹果"开头的记录

  • % 开头无法用索引:不知道从哪里开始匹配,只能全表扫描

  • 解决方案

    • 如果必须模糊搜索,考虑使用全文索引(FULLTEXT)
    • 或使用 Elasticsearch 等专业搜索引擎

(6)避免在 WHERE 中对字段进行表达式操作

案例对比

-- ❌ 不推荐(索引失效)
SELECT * FROM orders WHERE amount + 10 > 100;-- ✅ 推荐
SELECT * FROM orders WHERE amount > 90;

原因分析

  • 破坏索引结构:索引是对原始列值建立的,对列进行运算后,索引无法直接匹配
  • 优化器无法优化:数据库需要对每一行执行计算,再判断条件
  • 正确做法:将表达式移到等式右边

(7)避免在 WHERE 中对字段进行函数操作

案例对比

-- ❌ 不推荐(索引失效)
SELECT * FROM orders WHERE DATE(created_at) = '2024-01-01';
SELECT * FROM users WHERE UPPER(username) = 'ADMIN';-- ✅ 推荐
SELECT * FROM orders 
WHERE created_at >= '2024-01-01' AND created_at < '2024-01-02';-- 或创建函数索引(MySQL 8.0+)
CREATE INDEX idx_username_upper ON users((UPPER(username)));

原因分析

  • 函数计算开销:每行数据都要执行函数

  • 索引无法使用:索引是对原始值建立的,函数处理后的值不在索引中

  • 解决方案

    • 改写 SQL,避免函数包裹列
    • 使用函数索引或生成列

(8)复合索引遵循最左原则

案例

-- 创建复合索引
CREATE INDEX idx_user_status_date ON orders(user_id, status, created_at);-- ✅ 可以使用索引(最左列 user_id 存在)
SELECT * FROM orders WHERE user_id = 100;
SELECT * FROM orders WHERE user_id = 100 AND status = 'paid';
SELECT * FROM orders WHERE user_id = 100 AND status = 'paid' AND created_at > '2024-01-01';-- ❌ 无法使用索引(跳过了最左列)
SELECT * FROM orders WHERE status = 'paid';
SELECT * FROM orders WHERE created_at > '2024-01-01';

原因分析

  • 索引存储结构:复合索引按照 (user_id, status, created_at) 的顺序排序
  • 类似电话簿:先按姓氏排序,再按名字排序。如果不知道姓氏,无法快速查找名字
  • 优化建议:查询条件频繁的列放在前面

(9)左右外连接遵循小表驱动大表

案例

-- 假设:users 表 1000 行,orders 表 100万 行-- ❌ 不推荐(大表驱动小表)
SELECT * FROM orders o LEFT JOIN users u ON o.user_id = u.id;-- ✅ 推荐(小表驱动大表)
SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id;

原因分析

  • JOIN 算法

    :MySQL 使用嵌套循环连接(Nested Loop Join)

    • 外层表(驱动表)循环每一行
    • 对每一行,在内层表中查找匹配
  • 小表驱动大表效率高

    • 驱动表 1000 行 × 内层表索引查询(快)
    • 大表驱动:100万行 × 内层表查询(慢)
  • INNER JOIN:优化器会自动选择小表作为驱动表


(10)避免使用子查询,改用 JOIN

案例对比

-- ❌ 不推荐(子查询)
SELECT * FROM orders 
WHERE user_id IN (SELECT id FROM users WHERE status = 'active');-- ✅ 推荐(INNER JOIN)
SELECT o.* FROM orders o
INNER JOIN users u ON o.user_id = u.id
WHERE u.status = 'active';

原因分析

  • 子查询性能问题

    • MySQL 对子查询优化较弱,可能多次执行子查询
    • 需要在内存中创建临时表存储子查询结果
  • JOIN 优势

    • 优化器可以选择最优执行计划
    • 可以利用索引进行连接
    • 避免临时表开销

(11)大分页优化

案例对比

-- ❌ 不推荐(深度分页,越往后越慢)
SELECT * FROM orders LIMIT 999980, 20;
-- 需要扫描 1000000 行,丢弃前 999980 行-- ✅ 推荐(使用主键过滤)
SELECT * FROM orders WHERE id > 999980 LIMIT 20;-- ✅ 更好:记录上一页最大 ID
-- 第一页
SELECT * FROM orders ORDER BY id LIMIT 20;  -- 返回 id: 1-20
-- 第二页
SELECT * FROM orders WHERE id > 20 ORDER BY id LIMIT 20;  -- 返回 id: 21-40

原因分析

  • LIMIT 原理LIMIT offset, size 会扫描 offset + size 行,然后丢弃前 offset
  • 深度分页问题:offset 越大,扫描的无用数据越多
  • 优化原理:利用主键索引直接定位,避免扫描大量数据

(12)GROUP BY 优先使用 WHERE 而非 HAVING

案例对比

-- ❌ 不推荐(HAVING 过滤)
SELECT user_id, COUNT(*) as order_count
FROM orders
GROUP BY user_id
HAVING user_id > 1000;-- ✅ 推荐(WHERE 过滤)
SELECT user_id, COUNT(*) as order_count
FROM orders
WHERE user_id > 1000
GROUP BY user_id;

原因分析

  • 执行顺序

    1. WHERE:在分组前过滤,减少参与分组的数据量
    2. GROUP BY:对筛选后的数据分组
    3. HAVING:对分组后的结果过滤
  • WHERE 优势

    • 提前过滤,减少数据量
    • 可以使用索引
    • 减少排序和分组的开销
  • HAVING 使用场景:过滤聚合函数的结果

-- HAVING 的正确用法
SELECT user_id, COUNT(*) as order_count
FROM orders
GROUP BY user_id
HAVING COUNT(*) > 10;  -- 过滤订单数大于 10 的用户

总结表格

优化点核心原因关键收益
避免 *减少数据传输,启用覆盖索引降低 IO,提升查询速度
合理索引快速定位数据避免全表扫描
避免 NULL 判断索引不存储 NULL保证索引有效性
IN 替换 OR优化器处理更高效更好利用索引
LIKE 不用前缀 %保持索引有序性索引范围扫描
避免列运算/函数保持索引原始结构索引不失效
最左原则复合索引排序规则充分利用索引
小表驱动大表减少循环次数减少计算量
JOIN 代替子查询避免临时表,优化器更智能更好的执行计划
主键分页避免扫描无用数据深度分页不变慢
WHERE 优于 HAVING提前过滤数据减少分组开销

这些优化的本质都是减少扫描的数据量充分利用索引,从而提升 SQL 执行效率!

http://www.dtcms.com/a/528025.html

相关文章:

  • llama.cpp:Linux_x86端测试Qwen2.5-Omni
  • Java-160 MongoDB副本集部署实战 单机三实例/多机同法 10 分钟起集群 + 选举/读写/回滚全流程
  • 使用RedisTemplate设计一个消息队列?
  • 海龟交易系统R
  • 【攻防实战】Redis未授权RCE联动metasploit打穿三层内网(上)
  • 织梦网站图片修改不了wordpress模板开发 2016
  • .Net Framework 3.5下载安装教程(附安装包)
  • pycharm远程提交Git
  • PLM实施专家宝典:离散制造企业工程变更的“流程金融”方案
  • Orleans分布式系统架构详细分析
  • 建设网站的价钱深圳宝安上市公司网站建设报价
  • F034 vue+neo4j 体育知识图谱系统|体育文献知识图谱vue+flask知识图谱管理+d3.js可视化
  • 【day10】分治
  • 【Go】C++转Go:数据结构练习(一)排序算法
  • 每天学习一个新注解——@SafeVarargs
  • valgrind交叉编译android版本
  • 公司网站开发设计题目来源怎么写佛山免费建站怎样
  • 构建AI智能体:七十四、探索AI新纪元:扣子平台让想法到智能应用的极简之旅
  • P2119 [NOIP 2016 普及组] 魔法阵
  • 数据结构13:排序
  • 网站搭建 里短信wordpress acf破解版
  • 【C/C++】数据在内存中的存储
  • 我们项目中如何运用vueuse
  • 【开发者导航】集成多引擎与离线查询的macOS开源翻译工具:Easydict
  • 龙岗客户 IBM x3650 M5服务器system board fault故障,上门快修分享
  • TENGJUN-TYPE-C 24PIN(JX24-BPS015-A)连接器深度技术解析
  • 10.23作业
  • 深入剖析 Vue Router History 路由刷新页面 404 问题:原因与解决之道
  • FreeP2W:一个PDF转Word的CLI工具
  • .NET - .NET Aspire的Command-Line和GitHub Copilot