MySQL的10种高级SQL,性能起飞

前言

很多小伙伴在工作中,可能只把MySQL当作一个简单的"数据存储箱",用了它80%的基础功能,却不知道它还有另外20%的、能解决90%复杂问题的"高级用法"。

今天,我不谈基础的增删改查,就和你深入聊聊,在实际高性能、高并发、大数据量的场景下,那些真正能让你和团队生产力倍增、性能飞升的10种MySQL高级实战技巧。

希望对你会有所帮助。

01 执行计划

在优化任何查询之前,读懂EXPLAIN的输出是你的第一门必修课

它就像SQL的"X光片",能告诉你MySQL究竟打算如何执行你的查询,瓶颈在哪里。

核心用法与实战

执行EXPLAIN后,你需要重点关注以下几个关键字段:

  • type :访问类型,从最优到最差大致是:system > const > eq_ref > ref > range > index > ALL。看到ALL(全表扫描)就要警惕了。
  • key :实际使用的索引。如果为NULL,说明没用上索引。
  • rows:MySQL预估要扫描的行数。这个数字越接近实际需要的数据行数越好。
  • Extra :包含非常丰富的信息,例如Using filesort(需要额外排序)、Using temporary(使用了临时表),这通常是性能杀手。
sql 复制代码
-- 一个需要优化的查询示例
EXPLAIN 
SELECT * FROM orders 
WHERE user_id = 10086 
  AND create_time BETWEEN '2024-01-01' AND '2024-01-31'
ORDER BY amount DESC;

假设这个查询的typeALLkeyNULL。这意味着它在orders表上进行了全表扫描,性能极差。优化方法通常是创建一个复合索引:

sql 复制代码
-- 创建覆盖了WHERE和ORDER BY的复合索引
CREATE INDEX idx_user_time_amount ON orders(user_id, create_time, amount);
-- 再次使用EXPLAIN,你会看到type变成了range,key显示了新索引,性能天差地别。

深度剖析EXPLAIN是基于表的统计信息来估算成本的。如果表数据变化很大而统计信息未更新,优化器可能会选错索引。

这时,可以用ANALYZE TABLE table_name;来手动更新统计信息。

有些小伙伴在工作中写的SQL本身不复杂,但执行很慢,第一步就应该祭出EXPLAIN

02 高级索引策略

索引是性能的基石,但错误的索引比没有索引更糟糕。

高级索引策略

  1. 覆盖索引(Covering Index):如果索引包含了查询需要的所有字段,引擎就无需回表查询数据行,速度极快。
sql 复制代码
-- 假设常用查询是获取用户的姓名和邮箱
SELECT name, email FROM users WHERE age > 20;
-- 为这个查询设计覆盖索引
CREATE INDEX idx_age_name_email ON users(age, name, email);
-- age用于查询,name和email本身就在索引页中,无需查找数据行。
  1. 索引下推(Index Condition Pushdown, ICP) :这是MySQL 5.6引入的重大优化。对于复合索引(a, b),查询WHERE a = ? AND b LIKE '%xxx'。在旧版本中,即使a命中了索引,引擎也会将所有a=?的记录回表,再去过滤b。而ICP允许将b LIKE '%xxx'这个条件下推到存储引擎层,在索引扫描时就过滤,大大减少回表次数。

  2. 前缀索引(Prefix Index) :对于超长文本字段(如VARCHAR(500)),为整个字段建索引非常臃肿。可以只对前N个字符建立索引,在空间和效率间取得平衡。

sql 复制代码
-- 为content字段前100个字符创建索引
CREATE INDEX idx_content_prefix ON articles (content(100));
-- 缺点是前缀索引无法用于GROUP BY和ORDER BY操作。

深度剖析:索引是一把双刃剑,加速查询的同时,会降低写操作(INSERT/UPDATE/DELETE)的速度,因为索引树也需要维护。

一个表上创建十几个索引是常见的设计误区。

你需要定期使用SHOW INDEX FROM table_name;审查索引的基数(Cardinality,唯一值数量),删除使用率极低的冗余索引。

03 窗口函数

这是MySQL 8.0带来的"神兵利器",用于进行跨行计算,完美解决复杂排名、累加、移动平均等问题。

核心场景与语法

sql 复制代码
-- 经典场景:计算每个部门内员工的薪水排名
SELECT 
    name,
    department,
    salary,
    RANK() OVER (PARTITION BY department ORDER BY salary DESC) as dept_salary_rank,
    -- 同时计算公司整体排名
    RANK() OVER (ORDER BY salary DESC) as company_rank,
    -- 计算部门内薪水累计占比
    SUM(salary) OVER (PARTITION BY department) as dept_total,
    salary / SUM(salary) OVER (PARTITION BY department) as salary_ratio
FROM employees;

PARTITION BY类似于GROUP BY,但不会将行合并,而是定义窗口范围。

ORDER BY决定窗口内的排序。

深度剖析:在MySQL 8.0之前,要实现上述查询,你需要写复杂的自连接或效率极低的子查询。窗口函数在数据库内部进行了深度优化,性能提升可达几个数量级。

它特别适用于分析报表、实时排行榜、计算同比环比等OLAP型场景。

04 通用表表达式(CTE)

CTE(WITH子句)是另一个MySQL 8.0的重要特性,它允许你定义临时的命名结果集,在后续查询中像普通表一样引用。

优势与示例

  1. 提升可读性:将复杂查询分解成逻辑清晰的步骤。
  2. 支持递归:这是CTE的杀手级功能,可以轻松查询树形或图状数据。
sql 复制代码
-- 示例1:分解复杂查询(非递归)
WITH 
  high_value_orders AS ( -- 找出高价值订单
    SELECT user_id, SUM(amount) as total_spent 
    FROM orders 
    WHERE status = 'completed'
    GROUP BY user_id 
    HAVING total_spent > 10000
  ),
  active_users AS ( -- 找出活跃用户
    SELECT DISTINCT user_id 
    FROM user_logs 
    WHERE last_active_date > DATE_SUB(NOW(), INTERVAL 30 DAY)
  )
-- 最终查询:既是高价值又是活跃的用户
SELECT u.name, u.email, h.total_spent
FROM users u
JOIN high_value_orders h ON u.id = h.user_id
JOIN active_users a ON u.id = a.user_id;

-- 示例2:递归CTE,查询部门树
WITH RECURSIVE department_tree AS (
    -- 锚点:找到根部门
    SELECT id, name, parent_id, 1 as level 
    FROM departments 
    WHERE parent_id IS NULL
    UNION ALL
    -- 递归成员:连接父部门和子部门
    SELECT d.id, d.name, d.parent_id, dt.level + 1
    FROM departments d
    INNER JOIN department_tree dt ON d.parent_id = dt.id
)
SELECT * FROM department_tree ORDER BY level, id;

深度剖析 :递归CTE极大地简化了组织架构、分类目录、评论嵌套等层次数据的查询。

在旧版本中,这通常需要在应用层进行多次查询或在数据库中使用存储过程,递归CTE在数据库内核完成遍历,效率更高。

05 JSON类型与函数

MySQL 5.7+原生支持JSON数据类型,让你能够在关系型数据库中灵活地存储和查询半结构化数据,这在处理动态字段、配置信息或第三方API返回的数据时非常有用。

核心操作

sql 复制代码
-- 1. 创建包含JSON列的表
CREATE TABLE products (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    attributes JSON COMMENT '存储颜色、尺寸等动态属性'
);

-- 2. 插入JSON数据
INSERT INTO products VALUES (1, 'T-Shirt', '{"color": "red", "size": ["M", "L"], "tags": ["casual", "cotton"]}');

-- 3. 查询 (使用 -> 和 ->> 操作符)
-- -> 返回JSON类型, ->> 返回纯文本字符串
SELECT 
    name,
    attributes->>'$.color' as color, -- 提取color值
    attributes->'$.size' as size_array -- 提取size数组(仍为JSON)
FROM products 
WHERE attributes->>'$.color' = 'red' 
   OR JSON_CONTAINS(attributes->'$.tags', '"cotton"');

-- 4. 更新部分JSON
UPDATE products 
SET attributes = JSON_SET(attributes, '$.color', 'blue', '$.new_field', 'value') 
WHERE id = 1;

深度剖析:JSON列同样可以建立索引(通过函数索引),加速查询。

sql 复制代码
CREATE INDEX idx_color ON products( (attributes->>'$.color') );

这允许你在保持灵活性的同时,不丧失对关键字段的查询性能。

它完美填补了关系模型在应对多变业务需求时的短板。

06 分区表(Partitioning)

当单表数据量巨大(如数亿行)时,分区可以将一张大表在物理上分割为多个更小、更易管理的部分,而逻辑上仍是一张表。

分区策略与示例

sql 复制代码
-- 按时间范围(RANGE)分区,非常适合日志、订单表
CREATE TABLE sales (
    id INT,
    sale_date DATE,
    amount DECIMAL(10,2)
)
PARTITION BY RANGE COLUMNS(sale_date) (
    PARTITION p2023q1 VALUES LESS THAN ('2023-04-01'),
    PARTITION p2023q2 VALUES LESS THAN ('2023-07-01'),
    PARTITION p2023q3 VALUES LESS THAN ('2023-10-01'),
    PARTITION p2023q4 VALUES LESS THAN ('2024-01-01'),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

-- 查询时,优化器会自动定位到特定分区(分区裁剪,Partition Pruning)
EXPLAIN SELECT * FROM sales WHERE sale_date = '2023-05-15';
-- 你会看到partitions: p2023q2,意味着只扫描了2023年Q2的分区。

除了RANGE,还有LIST(按列表值)、HASH(按哈希值均匀分布)等分区方式。

深度剖析 :分区的核心优势在于维护性查询性能

你可以快速删除或归档整个旧分区(ALTER TABLE sales DROP PARTITION p2023q1;),这比DELETE操作快得多,且不产生碎片。对于按分区键过滤的查询,性能提升显著。

但注意,分区键选择不当或跨分区查询,性能可能反而下降。

07 连接(JOIN)与子查询

多表关联是业务常态,但写得不好就是性能灾难。

高级技巧

  1. 控制连接顺序 :MySQL优化器通常会选择它认为最佳的顺序,但你可以在复杂场景下用STRAIGHT_JOIN强制指定顺序。
sql 复制代码
SELECT ... 
FROM small_table s
STRAIGHT_JOIN large_table l ON s.id = l.s_id; -- 强制先查小表
  1. 利用衍生表(Derived Table)下推条件:有时将子查询或过滤条件提前,能极大地减少中间结果集。
sql 复制代码
-- 优化前:先连接两个大表,再过滤
SELECT * FROM A JOIN B ON A.id = B.aid WHERE A.create_time > '...';

-- 优化后:先过滤A表,再连接
SELECT * FROM (SELECT * FROM A WHERE create_time > '...') filtered_A
JOIN B ON filtered_A.id = B.aid;
  1. EXISTS vs IN :对于"是否存在"的查询,特别是子查询结果集较大时,EXISTS(关联子查询)通常比IN(非关联子查询)性能更好,因为它找到第一个匹配项就会停止。

深度剖析 :所有的JOIN优化,其核心思想都是 "尽早过滤,减少中间数据量" 。熟练使用EXPLAIN查看连接类型(如eq_ref很好,Using join buffer说明可能需要索引)是关键。

08 用户自定义变量

MySQL允许你定义用户变量(如@rank),这在一些需要跨行计算或记录中间状态的分析中非常有用。

实战案例:计算行间差值

sql 复制代码
-- 计算每日销售额的日环比增长率
SELECT 
    sale_date,
    daily_amount,
    -- 使用变量记录前一天的值
    @prev_amount as prev_day_amount,
    ROUND( (daily_amount - @prev_amount) / @prev_amount * 100, 2) as growth_rate,
    -- 将当前值赋给变量,供下一行使用
    @prev_amount := daily_amount
FROM 
    daily_sales_summary,
    (SELECT @prev_amount := 0) init -- 初始化变量
ORDER BY sale_date;

深度剖析:用户变量提供了过程式编程的能力,可以模拟窗口函数的部分功能(在MySQL 8.0之前)。

但它不是SQL标准,执行顺序有时反直觉,需谨慎使用。

在复杂的会话或事务中,变量的生命周期和作用域也需要仔细考量。

09 在线DDL与无锁变更

在业务7x24小时运行的时代,给大表加字段、改索引再也不能随意停服务了。

MySQL 5.6+提供了ALGORITHMLOCK选项,实现在线DDL(Online Data Definition)。

安全操作指南

sql 复制代码
-- 添加一个可为空且有默认值的新列,使用INPLACE算法和尽量低的锁级别
ALTER TABLE huge_table 
ADD COLUMN new_column VARCHAR(100) DEFAULT '' NOT NULL,
ALGORITHM=INPLACE, -- 尽量使用INPLACE(原地重建),避免COPY(锁表复制)
LOCK=NONE; -- 目标:不加锁,或共享锁

-- 修改列类型(某些情况需要COPY,会锁表)
ALTER TABLE huge_table 
MODIFY COLUMN old_column BIGINT,
ALGORITHM=COPY, -- 注意:这里可能必须用COPY
LOCK=SHARED;

深度剖析ALGORITHM=INPLACE意味着大部分工作(如重建索引)在引擎内部完成,允许并发DML操作。

ALGORITHM=COPY会创建新表并复制数据,全程锁表。

执行前务必用ALGORITHM=DEFAULT先测试一下。

pt-online-schema-change 是Percona提供的第三方工具,通过触发器实现真正的全程无锁,是更稳妥的选择。

10 利用生成列与函数索引

生成列的值由表中其他列计算而来,可分为虚拟列(VIRTUAL,不存储,读取时计算)和存储列(STORED,持久化存储)

这为建立高效的函数索引铺平了道路。

应用场景

sql 复制代码
-- 场景:经常需要根据 `first_name` 和 `last_name` 进行全名搜索
ALTER TABLE users 
ADD COLUMN full_name VARCHAR(255) 
GENERATED ALWAYS AS (CONCAT(first_name, ' ', last_name)) STORED, -- 创建存储的生成列
ADD INDEX idx_full_name (full_name); -- 在生成列上建立索引

-- 现在,以下查询可以高效使用索引
SELECT * FROM users WHERE full_name = 'John Doe';

深度剖析 :这解决了直接在表达式(如CONCAT(first_name, ' ', last_name))上建立函数索引的难题。

虚拟列节省空间但增加CPU计算开销;存储列反之。

更多项目实战在项目实战网:java突击队

总结:从"会用"到"精通"的跃迁

好了,一口气聊了11个MySQL的高级用法。让我们最后再梳理一下,这些技巧并非孤立存在,它们构成了一个应对不同场景挑战的工具箱。

高级用法 核心解决痛点 推荐适用场景
执行计划(EXPLAIN) 性能瓶颈可视化 所有慢查询优化前的第一步
高级索引策略 查询速度慢,写操作重 高频查询、大表性能优化
窗口函数(Window) 复杂跨行计算效率低 排行榜、数据分析、报表
通用表表达式(CTE) 复杂SQL难读写,树查询难 复杂业务逻辑拆分,层次数据查询
JSON类型 动态、半结构化数据存储与查询 配置、动态属性、API数据存储
分区表 超大表维护难,历史数据清理慢 时间序列数据(日志、订单)
JOIN/子查询优化 多表关联性能低下 涉及多个业务实体的复杂查询
用户自定义变量 需行间计算或状态保持(8.0前) 自定义序列、差值计算(有窗口函数后优先级降低)
在线DDL 业务不中断变更表结构 7x24小时系统表结构变更
生成列/函数索引 无法直接对表达式建索引 基于JSON字段、计算字段的高效查询

有些小伙伴在工作中可能会有这样的疑问:"我知道它们好,但该从哪里开始学起呢?"

我的建议是:EXPLAIN索引优化开始

这是性能问题的根本。

然后,根据你的业务需求,引入窗口函数CTE来简化复杂查询。当数据量上来后,考虑分区

对于动态数据结构,尝试JSON类型。

相关推荐
青云计划8 小时前
知光项目知文发布模块
java·后端·spring·mybatis
Victor3568 小时前
MongoDB(9)什么是MongoDB的副本集(Replica Set)?
后端
Victor3568 小时前
MongoDB(8)什么是聚合(Aggregation)?
后端
yeyeye11110 小时前
Spring Cloud Data Flow 简介
后端·spring·spring cloud
Tony Bai10 小时前
告别 Flaky Tests:Go 官方拟引入 testing/nettest,重塑内存网络测试标准
开发语言·网络·后端·golang·php
+VX:Fegn089511 小时前
计算机毕业设计|基于springboot + vue鲜花商城系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
程序猿阿伟11 小时前
《GraphQL批处理与全局缓存共享的底层逻辑》
后端·缓存·graphql
小小张说故事11 小时前
SQLAlchemy 技术入门指南
后端·python
识君啊11 小时前
SpringBoot 事务管理解析 - @Transactional 的正确用法与常见坑
java·数据库·spring boot·后端
想用offer打牌12 小时前
MCP (Model Context Protocol) 技术理解 - 第五篇
人工智能·后端·mcp