SQL优化完全指南:让你的数据库从"蜗牛"变"猎豹"!🐌➡️🐆

"天下武功,唯快不破!" ------ SQL优化大师

📚 目录


第一章:SQL优化到底是个啥?

1.1 引言:你的查询为啥这么慢?😭

想象一下这个场景:

你打开一个网页,点击查询按钮,然后...⏳

等啊等...☕

等啊等...🍵

等啊等...🥱

终于,30秒后,页面显示了结果。这时你的内心:

复制代码
╔════════════════════════════╗
║  这是1999年的网速吗???  ║
╚════════════════════════════╝

恭喜你,你遇到了SQL慢查询!这就像:

  • 🏃 别人的查询:博尔特百米冲刺(0.1秒)
  • 🐌 你的查询:蜗牛爬行(30秒)

1.2 SQL优化是什么?

简单来说,SQL优化就是:

让你的数据库查询从"蜗牛"变"猎豹"的魔法!

具体包括:

  • ✅ 让查询速度飞起来
  • ✅ 减少服务器资源消耗
  • ✅ 提升用户体验
  • ✅ 让老板给你加薪(最重要!💰)

1.3 为什么要学SQL优化?

生活比喻时间!🎭

场景一:超市购物

  • 没优化:进超市后,从第一排开始逐个货架找鸡蛋(全表扫描)
  • 优化后:直接看指示牌,去蛋类专区(索引查询)

场景二:图书馆找书

  • 没优化:从第一本书开始翻,一本本找《SQL优化指南》
  • 优化后:用检索系统,输入书名,直接定位

看到了吗?这就是优化的威力!

1.4 SQL性能问题的"罪魁祸首"

erlang 复制代码
性能杀手榜单 🏆
━━━━━━━━━━━━━━━━━━━━━━━━
👑 冠军:没有索引(占比40%)
🥈 亚军:全表扫描(占比30%)
🥉 季军:SELECT *(占比15%)
4️⃣ 殿军:不合理的JOIN(占比10%)
5️⃣ 其他:各种小毛病(占比5%)

第二章:索引------数据库的"任意门"🚪

2.1 索引到底是个啥?

生活比喻:字典查字

假设你要在字典里找"优化"这个词:

方法A(无索引):

erlang 复制代码
第1页:阿...不是
第2页:阿爸...不是
第3页:阿姨...不是
...
第800页:优化...找到了!😭

耗时:10分钟

方法B(有索引):

markdown 复制代码
1. 看拼音索引:you在第780-820页
2. 翻到第780页
3. 找到"优化"

耗时:10秒

这就是索引的威力!

2.2 索引的数据结构:B+树

不要被"B+树"吓到,我用生活例子解释:

css 复制代码
            [总目录]
           /    |    \
      [A-H]  [I-P]  [Q-Z]
       / \     / \     / \
    [A-D][E-H][I-L][M-P][Q-T][U-Z]
      |    |    |    |    |    |
    数据  数据  数据  数据  数据  数据

比喻:公司组织架构

  • 顶层:总裁(根节点)
  • 中层:部门经理(中间节点)
  • 底层:员工(叶子节点/数据)

找一个员工,不需要遍历所有人,只需:总裁→对应部门→找到员工

时间复杂度:

  • 无索引:O(n) ------ 线性查找
  • 有索引:O(log n) ------ 树查找

用人话说:

  • 100万条数据,无索引要查100万次
  • 有索引只需要查20次左右!

2.3 索引的类型

2.3.1 主键索引(PRIMARY KEY)

sql 复制代码
CREATE TABLE users (
    id INT PRIMARY KEY,  -- 主键索引,自动创建
    name VARCHAR(50)
);

特点:

  • 🔑 唯一性:每个值都不同
  • ⚡ 最快:直接定位
  • 📌 不能为NULL

生活比喻: 身份证号,独一无二!

2.3.2 唯一索引(UNIQUE INDEX)

sql 复制代码
CREATE TABLE users (
    id INT PRIMARY KEY,
    email VARCHAR(100),
    UNIQUE INDEX idx_email (email)  -- 唯一索引
);

特点:

  • ✨ 唯一性:值不能重复
  • 🆗 可以为NULL(但只能有一个NULL)

生活比喻: 手机号,每个人一个,但你可以暂时没有手机

2.3.3 普通索引(INDEX)

sql 复制代码
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    age INT,
    INDEX idx_age (age)  -- 普通索引
);

特点:

  • 🌈 最常用
  • 🔄 值可以重复
  • 💡 可以为NULL

生活比喻: 年龄,很多人可以同岁

2.3.4 联合索引(复合索引)

sql 复制代码
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    age INT,
    city VARCHAR(50),
    INDEX idx_age_city (age, city)  -- 联合索引
);

最左前缀原则(重要!)

假设索引是 (age, city):

ini 复制代码
✅ 能用上索引的查询:
WHERE age = 25                     -- 用到了age
WHERE age = 25 AND city = '北京'   -- 用到了age和city
WHERE age > 20 AND city = '上海'   -- 用到了age和city

❌ 用不上索引的查询:
WHERE city = '北京'                -- 跳过了age,用不上!

生活比喻:查电话簿

电话簿按照"姓氏→名字"排序:

复制代码
李明
李娜
王刚
王丽
张伟
张芳
  • ✅ 找"姓李的":很快,按姓氏找
  • ✅ 找"姓李名明的":更快,两个都用上
  • ❌ 找"名字叫明的":GG,要全扫描,因为没有姓氏

2.4 创建索引的正确姿势

2.4.1 什么情况下该建索引?

vbnet 复制代码
✅ 应该建索引:
├── WHERE条件中经常出现的列
├── JOIN连接条件的列
├── ORDER BY排序的列
├── GROUP BY分组的列
└── DISTINCT去重的列

❌ 不该建索引:
├── 表数据太少(几百条)
├── 频繁更新的列(写多读少)
├── 重复值太多的列(如性别:男/女)
└── 很少被查询的列

2.4.2 索引命名规范

sql 复制代码
-- 好的命名 ✅
INDEX idx_user_age          -- 普通索引
UNIQUE INDEX uk_user_email  -- 唯一索引
INDEX idx_order_user_date   -- 联合索引

-- 差的命名 ❌
INDEX index1                -- 啥意思?
INDEX abc                   -- 谁看得懂?

命名规则:

  • 普通索引:idx_表名_列名
  • 唯一索引:uk_表名_列名
  • 主键:pk_表名

2.5 索引的代价:没有免费的午餐

索引不是越多越好!

sql 复制代码
📊 索引的代价:
┌──────────────────────────────┐
│ 优点 👍     vs     缺点 👎   │
├──────────────────────────────┤
│ 查询快      vs     占空间    │
│ 排序快      vs     写入慢    │
│ JOIN快      vs     更新慢    │
│            vs     维护成本高  │
└──────────────────────────────┘

生活比喻:书的目录

  • 📖 目录多:找内容快,但书更厚,翻阅麻烦
  • 📕 目录少:书薄,但找内容慢

经验法则:

  • 一个表的索引数量:3-5个为宜
  • 联合索引优于多个单列索引

2.6 查看索引

sql 复制代码
-- 查看表的所有索引
SHOW INDEX FROM users;

-- 查看创建表的语句(含索引)
SHOW CREATE TABLE users;

2.7 删除索引

sql 复制代码
-- 删除索引
DROP INDEX idx_user_age ON users;

-- 删除主键
ALTER TABLE users DROP PRIMARY KEY;

第三章:EXPLAIN------SQL界的"透视眼"👁️

3.1 EXPLAIN是什么?

EXPLAIN就像:

  • 🩺 医生的X光机:看透身体内部
  • 🔍 侦探的放大镜:找出真相
  • 🎮 游戏的调试模式:看到隐藏数据

它能告诉你:

  • SQL怎么执行的?
  • 用了哪些索引?
  • 扫描了多少行?
  • 性能瓶颈在哪?

3.2 EXPLAIN的基本用法

sql 复制代码
-- 在查询前加上EXPLAIN
EXPLAIN SELECT * FROM users WHERE age = 25;

输出示例:

sql 复制代码
+----+-------------+-------+------+---------------+----------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key      | key_len | ref   | rows | Extra |
+----+-------------+-------+------+---------------+----------+---------+-------+------+-------+
|  1 | SIMPLE      | users | ref  | idx_age       | idx_age  | 5       | const |  100 | NULL  |
+----+-------------+-------+------+---------------+----------+---------+-------+------+-------+

看晕了?别怕,我一个个讲!

3.3 EXPLAIN字段详解

3.3.1 id:查询序列号

bash 复制代码
┌─────────────────────────────┐
│ id越大,越先执行            │
│ id相同,从上往下执行        │
└─────────────────────────────┘

示例:

sql 复制代码
EXPLAIN 
SELECT * FROM users WHERE id IN (
    SELECT user_id FROM orders WHERE amount > 1000
);
ini 复制代码
id=2: 先执行子查询orders表
id=1: 再执行主查询users表

生活比喻: 就像做饭的步骤:

  1. 先洗菜(id=2)
  2. 再炒菜(id=1)

3.3.2 select_type:查询类型

sql 复制代码
常见类型:
┌────────────────────────────────────┐
│ SIMPLE       - 简单查询(最常见)  │
│ PRIMARY      - 主查询              │
│ SUBQUERY     - 子查询              │
│ DERIVED      - 派生表(FROM子查询)│
│ UNION        - UNION查询           │
└────────────────────────────────────┘

3.3.3 type:访问类型(⭐超重要!)

这是性能的关键指标!从最优到最差:

sql 复制代码
性能排行榜:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🏆 system  ━━ 表只有一行(系统表)
🥇 const   ━━ 主键或唯一索引查询
🥈 eq_ref  ━━ 唯一索引查询
🥉 ref     ━━ 非唯一索引查询
4️⃣  range   ━━ 范围查询(BETWEEN、>、<)
5️⃣  index   ━━ 索引全扫描
6️⃣  ALL     ━━ 全表扫描(💀危险!)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━

生活比喻:找人的方式

ini 复制代码
type = const:   "找身份证号123456的人"
              → 唯一,瞬间定位 ⚡

type = ref:     "找所有25岁的人"
              → 多个结果,但很快 🏃

type = range:   "找20-30岁的人"
              → 范围查找,还行 🚶

type = index:   "看遍所有人的身份证"
              → 慢,但比ALL好 🐌

type = ALL:     "看遍所有人的所有信息"
              → 最慢!💀

优化目标:

sql 复制代码
✅ 至少达到 range 级别
✅ 最好达到 ref 级别
✅ 理想达到 const 级别
❌ 避免 index 和 ALL

3.3.4 possible_keys:可能用到的索引

sql 复制代码
EXPLAIN SELECT * FROM users WHERE age = 25 AND city = '北京';

-- 结果显示:
possible_keys: idx_age, idx_city, idx_age_city

意思是:MySQL发现有3个索引可以用

生活比喻: 去某地有3条路可选(高速、国道、小路)

3.3.5 key:实际使用的索引

sql 复制代码
key: idx_age_city

意思是:MySQL最终选了联合索引

生活比喻: 3条路里,选了高速(最快的)

⚠️ 重要情况:

ini 复制代码
如果 key = NULL:没用索引!危险!🚨

3.3.6 key_len:索引长度

用来判断联合索引用了几列

sql 复制代码
-- 假设索引是 (age, city)
-- age: INT (4字节) + 是否为NULL (1字节) = 5字节
-- city: VARCHAR(50) (50*3字节UTF8) + 长度 (2字节) + 是否为NULL (1字节) = 153字节

key_len = 5:     只用了age
key_len = 158:   用了age和city(5+153)

3.3.7 ref:索引的哪部分被使用

sql 复制代码
ref: const       -- 常量
ref: db.t1.id    -- 其他表的列
ref: func        -- 函数结果

3.3.8 rows:估计扫描行数(⭐重要!)

ini 复制代码
rows = 10:       扫描10行,很快 ✅
rows = 1000:     扫描1000行,还行 🆗
rows = 100000:   扫描10万行,慢 ⚠️
rows = 1000000:  扫描百万行,GG 💀

优化目标:rows越小越好!

3.3.9 Extra:额外信息(⭐重要!)

sql 复制代码
好的信息 ✅:
├── Using index          ━━ 覆盖索引,不用回表,最快!
├── Using index condition ━━ 索引条件下推
└── Using where          ━━ 使用WHERE过滤

需要注意的信息 ⚠️:
├── Using filesort       ━━ 文件排序,考虑加索引
├── Using temporary      ━━ 使用临时表,性能差
└── Using join buffer    ━━ JOIN缓冲,考虑加索引

危险信息 ❌:
├── Using where; Using temporary; Using filesort
└── Full table scan

3.4 EXPLAIN实战案例

案例1:可怕的全表扫描

sql 复制代码
EXPLAIN SELECT * FROM users WHERE name LIKE '%张%';

结果:

vbnet 复制代码
type: ALL
rows: 1000000
Extra: Using where

诊断:

  • 😱 type=ALL:全表扫描!
  • 😱 rows=100万:要扫描100万行!
  • 😱 没用索引:LIKE以%开头,索引失效

生活比喻: 在整个城市找"名字里有张字的人",需要挨家挨户问

解决方案:

sql 复制代码
-- 方案1:改变查询方式
WHERE name LIKE '张%'  -- 以"张"开头,可以用索引

-- 方案2:使用全文索引
ALTER TABLE users ADD FULLTEXT INDEX ft_name (name);
SELECT * FROM users WHERE MATCH(name) AGAINST('张');

案例2:索引失效的坑

sql 复制代码
EXPLAIN SELECT * FROM users WHERE age + 1 = 26;

结果:

yaml 复制代码
type: ALL
possible_keys: idx_age
key: NULL

诊断:

  • 😱 possible_keys有索引,但key=NULL:索引失效!
  • 😱 原因:列上有函数运算(age+1)

生活比喻: 图书馆按原书名排序,你要找"书名+序号=26的书" 图书管理员:我不会算术!只能一本本找!

解决方案:

sql 复制代码
-- 把运算放在常量侧
WHERE age = 25  -- 正确✅

案例3:完美的索引使用

sql 复制代码
EXPLAIN SELECT name, age FROM users WHERE age = 25;

-- 建立覆盖索引
CREATE INDEX idx_age_name ON users(age, name);

结果:

vbnet 复制代码
type: ref
key: idx_age_name
rows: 100
Extra: Using index

诊断:

  • ✅ type=ref:很好!
  • ✅ key用上了索引
  • ✅ rows只有100行
  • ✅ Extra=Using index:覆盖索引,不用回表!

生活比喻: 目录上直接有答案,不用翻正文!


第四章:查询优化的十八般武艺

4.1 避免SELECT *

❌ 错误示范:

sql 复制代码
SELECT * FROM users WHERE id = 1;

问题:

bash 复制代码
表结构:
┌────┬──────┬─────┬──────┬────────┬────────────────┬─────────────┐
│ id │ name │ age │ city │ email  │ address        │ description │
├────┴──────┴─────┴──────┴────────┴────────────────┴─────────────┤
│                     你只需要name和age                           │
│                    却把所有列都查出来了!                        │
└─────────────────────────────────────────────────────────────────┘

✅ 正确示范:

sql 复制代码
SELECT name, age FROM users WHERE id = 1;

好处:

  1. 减少网络传输
  2. 减少内存占用
  3. 可能使用覆盖索引(不用回表)

生活比喻:

sql 复制代码
去超市买鸡蛋和牛奶:

SELECT *:     把超市所有商品都搬回家 🏪🏠
             (包括你不需要的东西)

SELECT name, age:  只买鸡蛋和牛奶 🥚🥛
                  (精准高效)

4.2 避免在WHERE中使用函数

❌ 错误示范:

sql 复制代码
-- 问题1:列上使用函数
SELECT * FROM orders WHERE YEAR(order_date) = 2024;

-- 问题2:类型转换
SELECT * FROM users WHERE id = '123';  -- id是INT类型

-- 问题3:计算操作
SELECT * FROM products WHERE price * 0.8 < 100;

索引失效原理:

scss 复制代码
索引存储的是原始值:
2024-01-15
2024-02-20
2024-03-10
...

查询要YEAR(order_date):
需要对每行计算YEAR()
索引就无法使用了!

✅ 正确示范:

sql 复制代码
-- 正确1:把函数放在常量侧
SELECT * FROM orders 
WHERE order_date >= '2024-01-01' AND order_date < '2025-01-01';

-- 正确2:类型匹配
SELECT * FROM users WHERE id = 123;

-- 正确3:移动计算
SELECT * FROM products WHERE price < 125;  -- 100/0.8=125

生活比喻:

arduino 复制代码
场景:图书馆找书

错误方式:
馆员:"帮我找出版年份-2000=24的书"
图书管理员:"我只能按出版年份找,不会算术!"
结果:要逐本计算 😭

正确方式:
馆员:"帮我找2024年出版的书"
图书管理员:"好的,马上找到!"
结果:直接定位 ✅

4.3 避免使用OR

❌ 错误示范:

sql 复制代码
SELECT * FROM users WHERE age = 25 OR age = 30;

问题:

  • OR可能导致索引失效
  • 即使使用索引,也要扫描两次

✅ 正确示范:

sql 复制代码
-- 方案1:使用IN
SELECT * FROM users WHERE age IN (25, 30);

-- 方案2:使用UNION(数据量大时)
SELECT * FROM users WHERE age = 25
UNION ALL
SELECT * FROM users WHERE age = 30;

性能对比:

yaml 复制代码
OR:        扫描不确定,可能全表
IN:        走索引,一次扫描
UNION ALL: 走索引,两次精准扫描(无去重)
UNION:     走索引,两次扫描+去重

生活比喻:

arduino 复制代码
任务:找出班级里叫"小明"或"小红"的同学

OR方式:
从第一个同学开始问:
"你叫小明吗?或者叫小红吗?"
每个人都要问一遍 😓

IN方式:
直接看花名册,找小明和小红
精准定位 ✅

4.4 小心使用LIKE

❌ 错误示范:

sql 复制代码
-- 索引失效的情况
SELECT * FROM users WHERE name LIKE '%张%';   -- 前后都有%
SELECT * FROM users WHERE name LIKE '%张';    -- 前面有%

为什么失效?

sql 复制代码
索引是按顺序排列的:
张三
张四
李四
王五
赵六

LIKE '张%':   可以用索引,从"张"开始找 ✅
LIKE '%张%':  不知道从哪开始,只能全扫 ❌

✅ 正确示范:

sql 复制代码
-- 可以使用索引
SELECT * FROM users WHERE name LIKE '张%';

-- 需要模糊查询时,使用全文索引
ALTER TABLE users ADD FULLTEXT INDEX ft_name (name);
SELECT * FROM users WHERE MATCH(name) AGAINST('张');

生活比喻:

sql 复制代码
场景:字典查字

LIKE '张%':   
"找以'张'开头的字"
直接翻到zhang那页 ✅

LIKE '%张%':  
"找包含'张'的字"
每个字都要看一遍 😭

4.5 使用LIMIT分页优化

❌ 错误示范:

sql 复制代码
-- 深度分页问题
SELECT * FROM orders ORDER BY id LIMIT 100000, 10;

问题:

markdown 复制代码
要查第100000页的10条数据:
1. 先扫描100010行
2. 取最后10行
3. 丢弃前100000行

天哪!扫了10万行,只用10行!💀

✅ 优化方案:

方案1:使用上一页的最大ID

sql 复制代码
-- 假设上一页最大id是99999
SELECT * FROM orders 
WHERE id > 99999 
ORDER BY id 
LIMIT 10;

方案2:延迟关联

sql 复制代码
SELECT * FROM orders o
INNER JOIN (
    SELECT id FROM orders ORDER BY id LIMIT 100000, 10
) t ON o.id = t.id;

方案3:使用业务主键

sql 复制代码
-- 记录上次查询的最后一个时间戳
SELECT * FROM orders 
WHERE create_time > '2024-01-01 10:00:00'
ORDER BY create_time 
LIMIT 10;

生活比喻:

复制代码
场景:在一本厚书里翻到第1000页

错误方式:
从第1页开始,一页页翻
翻到第999页:累死了 😭

正确方式:
看书签,直接翻到第1000页附近 ✅

4.6 优化JOIN查询

4.6.1 JOIN的类型

scss 复制代码
┌─────────────────────────────────┐
│      LEFT JOIN (左连接)        │
│                                 │
│  ●●●●●●                          │
│  ●●●●●● ●●●                     │
│        ●●●●●●                   │
│   左表   右表                   │
│  (全部) (匹配)                  │
└─────────────────────────────────┘

┌─────────────────────────────────┐
│     INNER JOIN (内连接)        │
│                                 │
│  ●●●●●●                          │
│  ●●●●●● ●●●                     │
│        ●●●●●●                   │
│          ●●                      │
│        (交集)                    │
└─────────────────────────────────┘

4.6.2 JOIN优化技巧

技巧1:小表驱动大表

sql 复制代码
-- ❌ 错误:大表驱动小表
SELECT * FROM orders o  -- 100万行
INNER JOIN users u ON o.user_id = u.id  -- 1万行

-- ✅ 正确:小表驱动大表
SELECT * FROM users u  -- 1万行
INNER JOIN orders o ON u.id = o.user_id  -- 100万行

原理:

scss 复制代码
大表驱动小表:
for (100万次) {
    去小表找匹配 (1万次)
}
总共:100万 × 查找时间

小表驱动大表:
for (1万次) {
    去大表找匹配 (100万次,但有索引!)
}
总共:1万 × 索引查找时间(极快)

生活比喻:

scss 复制代码
任务:找出两个班级的同学配对

错误方式:
大班(100人)的每个同学,
去小班(10人)里找匹配
要问100次

正确方式:
小班(10人)的每个同学,
去大班(100人)里找匹配
只要问10次 ✅

技巧2:确保JOIN列有索引

sql 复制代码
-- 在外键列建索引
CREATE INDEX idx_user_id ON orders(user_id);

-- 然后JOIN
SELECT * FROM users u
INNER JOIN orders o ON u.id = o.user_id;

技巧3:避免在JOIN条件中使用函数

sql 复制代码
-- ❌ 错误
SELECT * FROM users u
INNER JOIN orders o ON DATE(o.create_time) = u.register_date;

-- ✅ 正确
SELECT * FROM users u
INNER JOIN orders o ON o.create_time >= u.register_date
                     AND o.create_time < DATE_ADD(u.register_date, INTERVAL 1 DAY);

4.7 避免隐式类型转换

❌ 错误示范:

sql 复制代码
CREATE TABLE users (
    id INT PRIMARY KEY,
    phone VARCHAR(11)
);

-- 查询时用数字
SELECT * FROM users WHERE phone = 13800138000;  -- phone是VARCHAR

发生了什么?

scss 复制代码
MySQL自动转换为:
WHERE CAST(phone AS SIGNED) = 13800138000

等于在phone列上加了函数!
索引失效!💀

✅ 正确示范:

sql 复制代码
-- 类型匹配
SELECT * FROM users WHERE phone = '13800138000';

记忆口诀:

sql 复制代码
VARCHAR配字符串 '123'
INT配数字 123
DATE配日期 '2024-01-01'
类型对上,索引飞!

4.8 优化COUNT查询

不同COUNT的区别:

sql 复制代码
-- 方式1:统计所有行
SELECT COUNT(*) FROM users;

-- 方式2:统计id列
SELECT COUNT(id) FROM users;

-- 方式3:统计非NULL的name
SELECT COUNT(name) FROM users;

-- 方式4:统计去重后的name
SELECT COUNT(DISTINCT name) FROM users;

性能对比:

scss 复制代码
COUNT(*) > COUNT(id) > COUNT(name) > COUNT(DISTINCT name)
  最快                                        最慢

为什么COUNT(*)最快?

sql 复制代码
COUNT(*):    MySQL优化过,直接读索引树的行数
COUNT(id):   要判断id是否为NULL
COUNT(name): 要判断name是否为NULL
COUNT(DISTINCT name): 还要去重

大表COUNT优化:

方案1:使用近似值

sql 复制代码
-- 从统计信息获取
EXPLAIN SELECT * FROM users;
-- 看rows字段,大概的行数

方案2:单独维护计数表

sql 复制代码
CREATE TABLE table_count (
    table_name VARCHAR(50) PRIMARY KEY,
    row_count INT
);

-- 插入时+1,删除时-1
-- 查询时直接读这个表
SELECT row_count FROM table_count WHERE table_name = 'users';

生活比喻:

scss 复制代码
统计体育馆人数:

COUNT(*):     
看电子计数器,瞬间知道 ⚡

逐个数:      
站门口数进来的人 😓

去重后数:    
还要记住谁来过,避免重复 💀

4.9 使用UNION ALL代替UNION

sql 复制代码
-- ❌ 慢:会去重
SELECT name FROM users WHERE age = 25
UNION
SELECT name FROM users WHERE age = 30;

-- ✅ 快:不去重
SELECT name FROM users WHERE age = 25
UNION ALL
SELECT name FROM users WHERE age = 30;

性能差异:

sql 复制代码
UNION:     查询 + 排序 + 去重
UNION ALL: 查询

如果确定没有重复数据,用UNION ALL!

生活比喻:

sql 复制代码
任务:合并两个班的学生名单

UNION:
合并后,检查有没有重复的学生
发现小明在两个班都有,去掉一个 😓

UNION ALL:
直接合并,不管重复 ✅
(如果确定没重复,这样最快)

4.10 批量操作代替循环单条

❌ 错误示范:

sql 复制代码
-- 插入1000条数据,执行1000次
for (int i = 0; i < 1000; i++) {
    INSERT INTO users VALUES (i, 'name' + i);
}

✅ 正确示范:

sql 复制代码
-- 批量插入,一次搞定
INSERT INTO users VALUES 
(1, 'name1'),
(2, 'name2'),
(3, 'name3'),
...
(1000, 'name1000');

性能对比:

yaml 复制代码
单条插入:  1000次网络往返 + 1000次事务
批量插入:  1次网络往返 + 1次事务

速度差异:10-100倍!

生活比喻:

复制代码
任务:搬1000块砖

单条方式:
一次搬一块,往返1000次 😭

批量方式:
用小推车,一次搬100块,往返10次 ✅

第五章:表设计优化------打好地基

5.1 选择合适的数据类型

原则:更小更好

sql 复制代码
┌──────────────────────────────────┐
│ 存储年龄:                       │
├──────────────────────────────────┤
│ ❌ BIGINT    - 8字节,太浪费     │
│ ❌ INT       - 4字节,还是大     │
│ ✅ TINYINT   - 1字节,完美!     │
│   (范围:0-255,够用了)          │
└──────────────────────────────────┘

常见类型选择:

sql 复制代码
-- ❌ 错误示例
CREATE TABLE users (
    id BIGINT,              -- 太大
    age INT,                -- 太大
    gender VARCHAR(50),     -- 太大
    is_vip TEXT,            -- 什么鬼
    salary DOUBLE           -- 会有精度问题
);

-- ✅ 正确示例
CREATE TABLE users (
    id INT UNSIGNED,        -- 42亿够用
    age TINYINT UNSIGNED,   -- 0-255
    gender CHAR(1),         -- '男'/'女'
    is_vip TINYINT(1),      -- 0/1
    salary DECIMAL(10,2)    -- 精确的钱
);

类型选择指南:

sql 复制代码
整数类型:
┌─────────────┬───────────┬─────────────────┐
│ 类型        │ 字节      │ 适用场景        │
├─────────────┼───────────┼─────────────────┤
│ TINYINT     │ 1字节     │ 年龄、状态      │
│ SMALLINT    │ 2字节     │ 编号            │
│ MEDIUMINT   │ 3字节     │ 大编号          │
│ INT         │ 4字节     │ 主键、外键      │
│ BIGINT      │ 8字节     │ 超大数据        │
└─────────────┴───────────┴─────────────────┘

字符类型:
┌─────────────┬─────────────────────────┐
│ CHAR(n)     │ 固定长度,如手机号      │
│ VARCHAR(n)  │ 可变长度,如姓名        │
│ TEXT        │ 长文本,如文章          │
└─────────────┴─────────────────────────┘

时间类型:
┌─────────────┬─────────────────────────┐
│ DATE        │ 日期 '2024-01-01'       │
│ DATETIME    │ 日期时间(8字节)       │
│ TIMESTAMP   │ 时间戳(4字节,推荐!)  │
└─────────────┴─────────────────────────┘

金额类型:
┌─────────────┬─────────────────────────┐
│ DECIMAL     │ 精确小数,存钱用这个!  │
│ DOUBLE      │ 浮点数,会有误差,慎用  │
└─────────────┴─────────────────────────┘

生活比喻:

makefile 复制代码
存储"性别":

BIGINT:   像用卡车运一个苹果 🚛🍎
VARCHAR(50): 像用小货车运一个苹果 🚐🍎
CHAR(1):  像用篮子刚好装一个苹果 🧺🍎 ✅

5.2 避免NULL值

为什么要避免NULL?

vbscript 复制代码
NULL的问题:
1. 占用额外空间存储NULL标记
2. 索引效率降低
3. 查询逻辑复杂(需要IS NULL判断)
4. 聚合函数会忽略NULL

❌ 错误示范:

sql 复制代码
CREATE TABLE users (
    id INT,
    name VARCHAR(50) DEFAULT NULL,
    age INT DEFAULT NULL,
    city VARCHAR(50) DEFAULT NULL
);

-- 查询复杂
SELECT * FROM users WHERE age IS NULL OR age >= 18;

✅ 正确示范:

sql 复制代码
CREATE TABLE users (
    id INT NOT NULL,
    name VARCHAR(50) NOT NULL DEFAULT '',
    age INT NOT NULL DEFAULT 0,
    city VARCHAR(50) NOT NULL DEFAULT ''
);

-- 查询简单
SELECT * FROM users WHERE age = 0 OR age >= 18;

生活比喻:

arduino 复制代码
问:"你今年多少岁?"

NULL回答:"我没有年龄"
程序员:"什么鬼???" 💀

默认值回答:"0岁"
程序员:"哦,还没出生或没填" ✅

5.3 合理使用反范式设计

三范式 vs 反范式

规范化(三范式):

sql 复制代码
-- 订单表
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    product_id INT
);

-- 用户表
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(50)
);

-- 查询需要JOIN
SELECT o.*, u.name 
FROM orders o
JOIN users u ON o.user_id = u.id;

反范式(冗余设计):

sql 复制代码
-- 订单表(冗余用户名)
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    user_name VARCHAR(50),  -- 冗余!
    product_id INT
);

-- 查询直接搞定
SELECT * FROM orders WHERE user_name = '张三';

权衡:

scss 复制代码
┌──────────────┬────────────────┬────────────────┐
│              │ 规范化(三范式)│ 反范式         │
├──────────────┼────────────────┼────────────────┤
│ 存储空间     │ 小 ✅          │ 大 ❌          │
│ 数据一致性   │ 高 ✅          │ 需要维护 ⚠️   │
│ 查询性能     │ 慢(需要JOIN) ❌ │ 快 ✅          │
│ 更新性能     │ 快 ✅          │ 慢 ❌          │
└──────────────┴────────────────┴────────────────┘

使用场景:

diff 复制代码
✅ 适合反范式:
- 读多写少的字段
- 经常JOIN查询的字段
- 高并发查询场景

❌ 不适合反范式:
- 频繁更新的字段
- 数据一致性要求高
- 存储空间紧张

生活比喻:

复制代码
三范式:
你的联系方式统一存在通讯录里
每次打电话要先查通讯录 📖📞

反范式:
你把常用的联系人手机号背下来
打电话直接拨 📞✅

5.4 分库分表

当数据量达到千万、亿级别时,单表扛不住了!

垂直分表

bash 复制代码
原表(列太多):
┌────┬──────┬─────┬──────┬─────────┬─────────────┐
│ id │ name │ age │ city │ address │ description │
└────┴──────┴─────┴──────┴─────────┴─────────────┘
          ↓ 拆分
┌────┬──────┬─────┬──────┐   ┌────┬─────────┬─────────────┐
│ id │ name │ age │ city │   │ id │ address │ description │
└────┴──────┴─────┴──────┘   └────┴─────────┴─────────────┘
    常用字段表                      详情字段表

水平分表

scss 复制代码
原表(行太多):
users (1亿条数据)
          ↓ 拆分
users_0 (2500万)
users_1 (2500万)
users_2 (2500万)
users_3 (2500万)

分片策略:

sql 复制代码
-- 按用户ID分片(取模)
user_id % 4 = 0 → users_0
user_id % 4 = 1 → users_1
user_id % 4 = 2 → users_2
user_id % 4 = 3 → users_3

生活比喻:

diff 复制代码
垂直分表:
原来所有东西都堆在一个房间
现在:
- 卧室放床和衣服(常用)
- 储藏室放杂物(不常用)

水平分表:
图书馆书太多:
- 一层放A-F开头的书
- 二层放G-M开头的书
- 三层放N-Z开头的书

第六章:高级优化技巧

6.1 查询缓存(MySQL 5.7及以前)

⚠️ 注意:MySQL 8.0已移除查询缓存

sql 复制代码
-- 查看缓存配置
SHOW VARIABLES LIKE 'query_cache%';

-- 开启缓存
SET GLOBAL query_cache_type = ON;
SET GLOBAL query_cache_size = 134217728;  -- 128MB

缓存原理:

sql 复制代码
第一次查询:
SQL → 执行 → 结果 → 缓存起来

第二次相同查询:
SQL → 发现缓存命中 → 直接返回结果 ⚡

失效情况:

scss 复制代码
❌ 表数据变了:缓存失效
❌ SQL差一个空格:不命中
❌ 使用NOW()等函数:不缓存

生活比喻:

ini 复制代码
背乘法口诀表:
第一次:1×1=1, 2×2=4...慢慢算
以后:直接背出来 ✅

但如果老师改了规则,乘法口诀表就得重新背

6.2 覆盖索引

什么是覆盖索引?

索引里已经包含了所有需要的列,不用回表查询!

sql 复制代码
-- 建立联合索引
CREATE INDEX idx_age_name ON users(age, name);

-- 查询只要age和name
SELECT age, name FROM users WHERE age = 25;

执行过程:

markdown 复制代码
普通查询(需要回表):
1. 去索引树找到满足条件的主键id
2. 拿着id回到数据表查name
3. 返回结果

覆盖索引(不需要回表):
1. 去索引树找到满足条件的记录
2. 索引里已经有name,直接返回
3. 省略了回表步骤!⚡

判断方法:

sql 复制代码
EXPLAIN SELECT age, name FROM users WHERE age = 25;

-- 如果Extra显示:Using index
-- 恭喜你!用上了覆盖索引!

生活比喻:

markdown 复制代码
查电话簿:

需要回表:
1. 在目录里找到"张三" - 第203页
2. 翻到第203页看详细信息

覆盖索引:
1. 目录里就有电话号码,直接看 ✅

6.3 前缀索引

当字段很长时,可以只索引前几个字符

sql 复制代码
-- 原始:索引完整的email
CREATE INDEX idx_email ON users(email);

-- 优化:只索引前10个字符
CREATE INDEX idx_email_prefix ON users(email(10));

选择前缀长度:

sql 复制代码
-- 查看选择性
SELECT 
    COUNT(DISTINCT LEFT(email, 5)) / COUNT(*) AS sel5,
    COUNT(DISTINCT LEFT(email, 10)) / COUNT(*) AS sel10,
    COUNT(DISTINCT LEFT(email, 15)) / COUNT(*) AS sel15
FROM users;

-- 选择选择性最高且最短的长度

权衡:

vbnet 复制代码
优点:
✅ 索引更小
✅ 查询更快
✅ 占用更少内存

缺点:
❌ 不能用于ORDER BY
❌ 不能用于GROUP BY
❌ 选择性可能降低

生活比喻:

sql 复制代码
给书编号:

完整索引:
《如何在30天内学会SQL优化并成为数据库大师》
太长了!

前缀索引:
《如何在30天内学会...》
够用了,也更短 ✅

6.4 索引下推(ICP)

MySQL 5.6+的优化特性

sql 复制代码
CREATE INDEX idx_age_name ON users(age, name);

-- 查询
SELECT * FROM users WHERE age > 20 AND name LIKE '张%';

没有ICP:

markdown 复制代码
1. 使用索引找到所有age>20的记录(比如1万条)
2. 回表1万次,读取完整数据
3. 在Server层过滤name LIKE '张%'

有ICP:

markdown 复制代码
1. 使用索引找到age>20的记录
2. 在索引中直接过滤name LIKE '张%'(剩下100条)
3. 只回表100次

性能提升:

复制代码
回表次数:10000 → 100
提升:100倍!

查看是否使用:

sql 复制代码
EXPLAIN ...
-- Extra显示:Using index condition

生活比喻:

arduino 复制代码
招聘:要求年龄25-35岁,名字叫"张*"

没有ICP:
先把所有25-35岁的人叫来面试(1000人)
面试时再问名字,筛选姓张的(10人)

有ICP:
直接从简历(索引)中筛选:
25-35岁 + 姓张(10人)
只让这10人来面试 ✅

6.5 分区表

将大表按规则分成多个子表

sql 复制代码
-- 按年份分区
CREATE TABLE orders (
    id INT,
    user_id INT,
    order_date DATE,
    amount DECIMAL(10,2)
)
PARTITION BY RANGE (YEAR(order_date)) (
    PARTITION p2021 VALUES LESS THAN (2022),
    PARTITION p2022 VALUES LESS THAN (2023),
    PARTITION p2023 VALUES LESS THAN (2024),
    PARTITION p2024 VALUES LESS THAN (2025)
);

查询优化:

sql 复制代码
-- 只查2023年的数据
SELECT * FROM orders WHERE order_date >= '2023-01-01' 
                         AND order_date < '2024-01-01';

-- MySQL只扫描p2023分区!

分区策略:

sql 复制代码
RANGE分区:  按范围(如日期、ID)
LIST分区:   按列表值(如地区)
HASH分区:   按哈希值
KEY分区:    类似HASH,MySQL自动处理

生活比喻:

diff 复制代码
图书馆分区:
- A区:2020年的书
- B区:2021年的书
- C区:2022年的书
- D区:2023年的书

找2023年的书,直接去D区
不用翻其他区 ✅

第七章:实战案例大解析

案例1:慢查询优化------从30秒到0.1秒

背景

某电商订单表,1000万数据,查询超时

原始SQL:

sql 复制代码
SELECT * FROM orders 
WHERE user_id = 12345 
  AND status = 'paid'
  AND DATE(create_time) = '2024-01-15';

问题诊断:

sql 复制代码
EXPLAIN SELECT ...;

结果:

vbnet 复制代码
type: ALL
rows: 10000000
Extra: Using where

问题分析:

ini 复制代码
😱 type=ALL:全表扫描
😱 rows=1000万:要扫描全表
😱 DATE(create_time):索引失效
😱 SELECT *:查了不需要的列

优化步骤:

Step 1:创建索引

sql 复制代码
CREATE INDEX idx_user_status_time 
ON orders(user_id, status, create_time);

Step 2:去掉函数

sql 复制代码
-- 把DATE(create_time) = '2024-01-15'
-- 改为范围查询
WHERE create_time >= '2024-01-15 00:00:00'
  AND create_time < '2024-01-16 00:00:00'

Step 3:只查需要的列

sql 复制代码
SELECT id, order_no, amount, create_time

优化后的SQL:

sql 复制代码
SELECT id, order_no, amount, create_time
FROM orders 
WHERE user_id = 12345 
  AND status = 'paid'
  AND create_time >= '2024-01-15 00:00:00'
  AND create_time < '2024-01-16 00:00:00';

验证效果:

sql 复制代码
EXPLAIN SELECT ...;

结果:

vbnet 复制代码
type: ref
key: idx_user_status_time
rows: 10
Extra: Using index condition

性能对比:

复制代码
优化前:30秒,扫描1000万行
优化后:0.1秒,扫描10行
提升:300倍!🚀

案例2:分页查询优化------深度分页问题

背景

用户列表分页,第1000页加载超慢

原始SQL:

sql 复制代码
SELECT * FROM users ORDER BY id LIMIT 999000, 1000;

问题:

markdown 复制代码
要获取第1000页(每页1000条):
1. MySQL扫描100万行
2. 取最后1000行
3. 前面99.9万行白扫描了!💀

优化方案一:使用ID范围

sql 复制代码
-- 记录上一页的最大ID(假设是999000)
SELECT * FROM users 
WHERE id > 999000 
ORDER BY id 
LIMIT 1000;

性能:

复制代码
优化前:扫描100万行
优化后:扫描1000行
提升:1000倍!

缺点:

  • 需要记录上一页的ID
  • 不能跳页

优化方案二:延迟关联

sql 复制代码
SELECT * FROM users u
INNER JOIN (
    SELECT id FROM users ORDER BY id LIMIT 999000, 1000
) t ON u.id = t.id;

原理:

bash 复制代码
子查询只查id,走覆盖索引
主查询用id关联,精准定位

性能:

复制代码
优化前:扫描完整行100万次
优化后:索引扫描100万次 + 回表1000次
提升:10倍以上

案例3:JOIN优化------订单用户查询

背景

查询订单及其用户信息

原始SQL:

sql 复制代码
SELECT o.*, u.name, u.phone
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE o.status = 'paid'
  AND u.city = '北京';

问题诊断:

sql 复制代码
EXPLAIN SELECT ...;

发现:

markdown 复制代码
1. orders表全表扫描
2. user_id没有索引
3. city没有索引

优化步骤:

Step 1:创建索引

sql 复制代码
CREATE INDEX idx_status ON orders(status);
CREATE INDEX idx_user_id ON orders(user_id);
CREATE INDEX idx_city ON users(city);

Step 2:改写SQL(先过滤再JOIN)

sql 复制代码
SELECT o.*, u.name, u.phone
FROM (
    SELECT * FROM orders WHERE status = 'paid'
) o
INNER JOIN (
    SELECT * FROM users WHERE city = '北京'
) u ON o.user_id = u.id;

Step 3:如果只需要部分字段,使用覆盖索引

sql 复制代码
-- 创建覆盖索引
CREATE INDEX idx_id_name_phone ON users(id, name, phone);

SELECT o.id, o.order_no, u.name, u.phone
FROM orders o
INNER JOIN users u ON o.user_id = u.id
WHERE o.status = 'paid' AND u.city = '北京';

性能对比:

复制代码
优化前:扫描百万行,耗时5秒
优化后:扫描千行,耗时0.05秒
提升:100倍!

案例4:COUNT优化------统计大表数据

背景

统计订单总数,超慢

原始SQL:

sql 复制代码
SELECT COUNT(*) FROM orders WHERE status = 'paid';

问题:

复制代码
表有1000万行,即使有索引,也要扫描很久

优化方案一:使用近似值

sql 复制代码
-- 从统计信息获取
EXPLAIN SELECT * FROM orders WHERE status = 'paid';
-- 看rows字段

优化方案二:单独维护计数表

sql 复制代码
-- 创建计数表
CREATE TABLE order_stats (
    status VARCHAR(20) PRIMARY KEY,
    count INT DEFAULT 0
);

-- 插入初始值
INSERT INTO order_stats VALUES ('paid', 0);

-- 订单插入时,计数+1(用触发器)
CREATE TRIGGER tr_order_insert
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
    UPDATE order_stats 
    SET count = count + 1 
    WHERE status = NEW.status;
END;

-- 查询时直接读计数表
SELECT count FROM order_stats WHERE status = 'paid';

性能对比:

复制代码
优化前:扫描1000万行,5秒
优化后:读1行,0.001秒
提升:5000倍!

第八章:常见坑点与避坑指南

8.1 索引失效的十大场景

sql 复制代码
🚨 索引失效现场 🚨

1️⃣ 列上有函数或运算
WHERE YEAR(create_time) = 2024       ❌
WHERE create_time >= '2024-01-01'    ✅

2️⃣ 类型不匹配
WHERE phone = 13800138000            ❌ (phone是VARCHAR)
WHERE phone = '13800138000'          ✅

3️⃣ LIKE以%开头
WHERE name LIKE '%张%'               ❌
WHERE name LIKE '张%'                ✅

4️⃣ 使用OR连接
WHERE age = 25 OR age = 30           ❌
WHERE age IN (25, 30)                ✅

5️⃣ 不等于判断
WHERE age != 25                      ❌
WHERE age > 25 OR age < 25           ⚠️ (看情况)

6️⃣ IS NOT NULL
WHERE name IS NOT NULL               ❌
建表时用NOT NULL + DEFAULT           ✅

7️⃣ NOT IN / NOT EXISTS
WHERE id NOT IN (1,2,3)              ❌
WHERE id NOT EXISTS (...)            ❌

8️⃣ 联合索引不满足最左前缀
INDEX(age, city)
WHERE city = '北京'                  ❌ (跳过了age)
WHERE age = 25                       ✅

9️⃣ 范围查询后面的索引列
INDEX(age, city, name)
WHERE age > 20 AND city = '北京'     ⚠️ (city不走索引)

🔟 索引列参与运算
WHERE age + 1 = 26                   ❌
WHERE age = 25                       ✅

8.2 性能杀手榜

markdown 复制代码
💀 性能杀手TOP 10 💀
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 全表扫描 (type=ALL)
2. SELECT *
3. 没有索引
4. 索引失效
5. 深度分页 (LIMIT 100000, 10)
6. 大量JOIN
7. 子查询
8. DISTINCT
9. 临时表 (Using temporary)
10. 文件排序 (Using filesort)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

8.3 SQL优化口诀

sql 复制代码
📜 SQL优化十字箴言 📜
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
索引加对,查询飞快
函数别碰,类型要对
小表驱动,别反着来
星号少用,按需查询
分页优化,别傻乎乎
JOIN有度,不要贪多
定期分析,清理维护
监控警告,问题早知
测试测试,反复测试
持续学习,不断进步
━━━━━━━━━━━━━━━━━━━━━━━━━━━━

8.4 开发规范建议

sql 复制代码
✅ 强制规范:
├── 所有表必须有主键
├── 外键列必须有索引
├── 禁止SELECT *
├── 禁止在WHERE中对列使用函数
├── VARCHAR长度不超过5000
├── 单表列数不超过50
├── 单表索引不超过5个
└── 禁止使用TEXT/BLOB做主键

⚠️ 推荐规范:
├── 表名、字段名使用小写
├── 避免使用保留字
├── 创建时间、更新时间字段
├── 逻辑删除代替物理删除
├── 金额使用DECIMAL类型
├── 使用UNSIGNED存储非负数
└── 合理使用NOT NULL DEFAULT

🔍 性能规范:
├── 单表数据不超过500万行
├── 单次批量插入不超过5000条
├── 事务控制在2秒内完成
├── 慢查询阈值设为1秒
└── 定期ANALYZE TABLE更新统计

总结:SQL优化的修炼之路

🎯 优化的三个境界

sql 复制代码
第一境界:能用就行
├── 功能实现了,但慢
├── 没有索引,全靠运气
├── SELECT * 遍地跑
└── 出了问题不知道咋办

第二境界:追求性能
├── 知道建索引
├── 会用EXPLAIN分析
├── 注意查询优化
└── 能解决常见性能问题

第三境界:炉火纯青
├── 懂原理,知道为什么
├── 能预见问题并避免
├── 设计阶段就考虑性能
├── 持续监控和优化
└── 经验丰富,游刃有余

🚀 优化步骤总结

sql 复制代码
┌─────────────────────────────────┐
│  SQL优化标准流程                │
├─────────────────────────────────┤
│ 1. 发现问题                     │
│    └── 监控慢查询日志           │
│                                 │
│ 2. 分析问题                     │
│    └── EXPLAIN查看执行计划      │
│                                 │
│ 3. 定位问题                     │
│    ├── 有没有索引?             │
│    ├── 索引有没有用上?         │
│    ├── 扫描了多少行?           │
│    └── 有没有临时表/文件排序?  │
│                                 │
│ 4. 解决问题                     │
│    ├── 加索引                   │
│    ├── 改SQL                    │
│    ├── 改表结构                 │
│    └── 拆表                     │
│                                 │
│ 5. 验证效果                     │
│    ├── 再次EXPLAIN              │
│    ├── 测试查询时间             │
│    └── 观察线上效果             │
│                                 │
│ 6. 持续监控                     │
│    └── 保持警惕,及时发现新问题 │
└─────────────────────────────────┘

📚 学习资源推荐

sql 复制代码
📖 书籍推荐:
├── 《高性能MySQL》 - 必读圣经
├── 《MySQL技术内幕:InnoDB存储引擎》
├── 《数据库索引设计与优化》
└── 《SQL性能调优》

🌐 在线资源:
├── MySQL官方文档
├── 掘金/思否等技术社区
├── GitHub优秀项目源码
└── 各大厂技术博客

🛠️ 工具推荐:
├── pt-query-digest(慢查询分析)
├── MySQLTuner(配置优化)
├── Percona Toolkit(性能工具集)
└── Navicat/DataGrip(GUI工具)

后记:优化永无止境

SQL优化是一个持续的过程,没有最好,只有更好。

记住这三句话:

复制代码
┌─────────────────────────────────────┐
│                                     │
│   1. 没有绝对的最优解               │
│      只有最适合当前场景的方案       │
│                                     │
│   2. 过早优化是万恶之源             │
│      但不优化是更大的恶             │
│                                     │
│   3. 实践出真知                     │
│      理论+实践=成功                 │
│                                     │
└─────────────────────────────────────┘

最后,送给大家一句话:

"慢即是快,快即是慢。 打好基础,步步为营, 总有一天,你的SQL会像火箭一样快!" 🚀


附录:常用SQL优化命令速查

sql 复制代码
-- ============ 索引管理 ============
-- 创建索引
CREATE INDEX idx_name ON table_name(column_name);
CREATE UNIQUE INDEX uk_name ON table_name(column_name);
CREATE INDEX idx_name ON table_name(col1, col2);  -- 联合索引

-- 查看索引
SHOW INDEX FROM table_name;

-- 删除索引
DROP INDEX idx_name ON table_name;

-- ============ 性能分析 ============
-- 查看执行计划
EXPLAIN SELECT ...;
EXPLAIN FORMAT=JSON SELECT ...;  -- JSON格式

-- 查看实际执行
EXPLAIN ANALYZE SELECT ...;  -- MySQL 8.0+

-- 查看慢查询配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

-- 开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;  -- 1秒

-- ============ 统计信息 ============
-- 更新统计信息
ANALYZE TABLE table_name;

-- 查看表统计
SHOW TABLE STATUS LIKE 'table_name';

-- 查看索引统计
SHOW INDEX FROM table_name;

-- ============ 优化命令 ============
-- 优化表(碎片整理)
OPTIMIZE TABLE table_name;

-- 检查表
CHECK TABLE table_name;

-- 修复表
REPAIR TABLE table_name;

-- ============ 性能监控 ============
-- 查看当前连接
SHOW PROCESSLIST;

-- 杀死慢查询
KILL 线程ID;

-- 查看InnoDB状态
SHOW ENGINE INNODB STATUS;

-- 查看表大小
SELECT 
    table_name,
    table_rows,
    ROUND(data_length/1024/1024, 2) AS 'data_mb',
    ROUND(index_length/1024/1024, 2) AS 'index_mb'
FROM information_schema.tables
WHERE table_schema = 'your_database';

全文完

感谢阅读!如果这篇指南对你有帮助,请给个⭐!

有问题欢迎讨论,让我们一起进步!💪

markdown 复制代码
  _____ ____    _        ___        _   _           _          _   _             
 / ____|  _ \  | |      / _ \      | | (_)         (_)        | | (_)            
| (___ | |_) | | |     | | | |_ __ | |_ _ _ __ ___  _ ______ _| |_ _  ___  _ __  
 \___ \|  _ <  | |     | | | | '_ \| __| | '_ ` _ \| |_  / _` | __| |/ _ \| '_ \ 
 ____) | |_) | | |____ | |_| | |_) | |_| | | | | | | |/ / (_| | |_| | (_) | | | |
|_____/|____/  |______| \___/| .__/ \__|_|_| |_| |_|_/___\__,_|\__|_|\___/|_| |_|
                              | |                                                  
                              |_|                                                  

🎉 让数据库飞起来! 🚀
相关推荐
短剑重铸之日17 小时前
《SpringBoot4.0初识》第一篇:前瞻与思想
java·开发语言·后端·spring·springboot4.0
it_czz17 小时前
LangSmith vs LangFlow vs LangGraph Studio 可视化配置方案对比
后端
蓝色王者17 小时前
springboot 2.6.13 整合flowable6.8.1
java·spring boot·后端
花哥码天下18 小时前
apifox登录后设置token到环境变量
java·后端
hashiqimiya19 小时前
springboot事务触发滚动与不滚蛋
java·spring boot·后端
TeamDev19 小时前
基于 Angular UI 的 C# 桌面应用
前端·后端·angular.js
PPPHUANG19 小时前
一次 CompletableFuture 误用,如何耗尽 IO 线程池并拖垮整个系统
java·后端·代码规范
用户83562907805120 小时前
用Python轻松管理Word页脚:批量处理与多节文档技巧
后端·python
想用offer打牌20 小时前
一站式了解Spring AI Alibaba的流式输出
java·人工智能·后端
秋说20 小时前
华为 DevKit 25.2.rc1 源码迁移分析使用教程(openEuler + ARM64)
后端