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 复制代码
  _____ ____    _        ___        _   _           _          _   _             
 / ____|  _ \  | |      / _ \      | | (_)         (_)        | | (_)            
| (___ | |_) | | |     | | | |_ __ | |_ _ _ __ ___  _ ______ _| |_ _  ___  _ __  
 \___ \|  _ <  | |     | | | | '_ \| __| | '_ ` _ \| |_  / _` | __| |/ _ \| '_ \ 
 ____) | |_) | | |____ | |_| | |_) | |_| | | | | | | |/ / (_| | |_| | (_) | | | |
|_____/|____/  |______| \___/| .__/ \__|_|_| |_| |_|_/___\__,_|\__|_|\___/|_| |_|
                              | |                                                  
                              |_|                                                  

🎉 让数据库飞起来! 🚀
相关推荐
大巨头3 小时前
豆包帮忙梳理知识点,真强大!
后端
疯狂的程序猴3 小时前
Vue前端开发工具大全,从编码到调试的高效工作流指南
后端
渣哥3 小时前
别再乱用了!Spring AOP 与 AspectJ 的区别比你想的复杂
javascript·后端·面试
hui函数3 小时前
Python全栈(基础篇)——Day10:后端内容(map+reduce+filter+sorted+实战演示+每日一题)
后端·python
hui函数3 小时前
Python全栈(基础篇)——Day13:后端内容(模块详解)
后端·python
摆烂工程师4 小时前
什么是 ChatGPT Business 会员?与 ChatGPT Plus 有什么不同?
前端·后端·程序员
格物致知5704 小时前
Nacos 使用说明
后端
用户68545375977694 小时前
零拷贝技术完全指南:让数据坐上"直达高铁"!🚄
后端
该用户已不存在4 小时前
我的Python工具箱,不用加班的秘密
前端·后端·python