MySQL - 性能优化

使用 Explain 进行分析

Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explain 结果来优化查询语句。

比较重要的字段有:

  • select_type : 查询类型,有简单查询、联合查询、子查询等
  • key : 使用的索引
  • rows : 扫描的行数

优化数据访问

1. 减少请求的数据量

  • 只返回必要的列: 最好不要使用 SELECT * 语句。
  • 只返回必要的行: 使用 LIMIT 语句来限制返回的数据。
  • 缓存重复查询的数据: 使用缓存可以避免在数据库中进行查询,特别在要查询的数据经常被重复查询时,缓存带来的查询性能提升将会是非常明显的。

2. 减少服务器端扫描的行数

最有效的方式是使用索引来覆盖查询。

重构查询方式

1. 切分大查询(Batch Delete)

原方案

复制代码
DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH);

问题

  • 一次性删除大量数据可能导致:
    • 长事务:锁定大量数据,阻塞其他操作。
    • 日志膨胀:事务日志(如 InnoDB 的 undo log)被占满。
    • 主从延迟:大事务在复制时延迟严重。

优化方案:分批删除

复制代码
rows_affected = 0
do {
    rows_affected = do_query(
        "DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH) LIMIT 10000"
    )
} while rows_affected > 0

优点

  • 减少锁持有时间:每次只锁 1 万行,避免长时间阻塞。
  • 控制资源占用:避免单次事务过大。
  • 可中断性:随时停止不会导致部分数据未删除。

注意事项

  1. 加索引 :确保 create 字段有索引,否则分批删除依然全表扫描。

  2. 避免间隙锁 :如果使用 InnoDB,建议改用 id 范围分批(避免间隙锁冲突):

    DELETE FROM messages WHERE id BETWEEN 1 AND 10000 AND create < DATE_SUB(NOW(), INTERVAL 3 MONTH);

  3. 休眠间隔:每次删除后休眠一段时间(如 100ms),减少对正常业务的影响。


2. 分解大连接查询(Join Decomposition)

原方案(大连接查询)

复制代码
SELECT * FROM tag
JOIN tag_post ON tag_post.tag_id = tag.id
JOIN post ON tag_post.post_id = post.id
WHERE tag.tag = 'mysql';

潜在问题

  • 缓存失效:任一表更新会导致整个查询缓存失效。
  • 锁竞争:多表连接可能跨表加锁。
  • 执行计划复杂:优化器可能选择低效的连接顺序。

优化方案:分解为单表查询 + 应用层关联

复制代码
-- 第一步:查询 tag 表
SELECT * FROM tag WHERE tag = 'mysql';
-- 假设返回 tag.id = 1234

-- 第二步:查询 tag_post 表
SELECT * FROM tag_post WHERE tag_id = 1234;
-- 假设返回 post_id 列表 (123, 456, 567)

-- 第三步:查询 post 表
SELECT * FROM post WHERE id IN (123, 456, 567);

优点

  1. 缓存友好
    • 单表查询结果可独立缓存(如 tag.tag='mysql' 的结果被缓存后,其他查询可复用)。
    • 连接查询的缓存一旦失效,所有表关联结果均失效。
  1. 减少锁竞争
    • 单表查询锁定的范围更小。
    • 避免多表连接时潜在的锁升级(如行锁升级为表锁)。
  1. 灵活扩展
    • 易于分库分表(如 post 表和 tag_post 表可部署在不同节点)。
  1. 执行效率
    • MySQL 对 IN() 列表的查询会按主键顺序访问,可能比随机连接更高效(尤其对机械硬盘)。

注意事项

  1. 网络开销:应用层关联需多次查询,适合低延迟网络环境。
  2. IN() 列表限制
    • MySQL 的 IN() 列表长度有限制(默认 max_allowed_packet 限制)。

    • 超长列表可改用临时表或分批查询:

      -- 使用临时表
      CREATE TEMPORARY TABLE temp_post_ids (id INT PRIMARY KEY);
      INSERT INTO temp_post_ids VALUES (123), (456), (567);
      SELECT * FROM post WHERE id IN (SELECT id FROM temp_post_ids);

  1. 事务一致性
    • 多次查询可能读到中间状态(需在事务中执行或使用快照读)。

何时选择分解连接查询?

|-------------------|---------------|---------------|
| 场景 | 适合分解 | 适合直接连接 |
| 查询缓存命中率高 | ⭐️ 单表查询缓存更易复用 | ❌ 连接查询缓存易失效 |
| 表数据更新频繁 | ⭐️ 减少锁竞争和缓存失效 | ❌ 大连接可能阻塞其他操作 |
| 分库分表架构 | ⭐️ 天然支持跨节点查询 | ❌ 跨节点连接难以实现 |
| 简单查询(如主键查询) | ❌ 额外网络开销不划算 | ⭐️ 直接连接更高效 |
| 复杂聚合计算(如 SUM) | ❌ 应用层聚合性能差 | ⭐️ 数据库聚合更高效 |


其他优化补充

(1)替代 IN() 的方案
  • 临时表:如前述,适合超长列表。

  • JOIN 替代 IN

    SELECT post.* FROM post
    JOIN (SELECT 123 AS id UNION SELECT 456 UNION SELECT 567) AS ids
    ON post.id = ids.id;

(2)连接查询的优化技巧

如果必须使用连接查询:

  1. 确保关联字段有索引 (如 tag_post.tag_idtag_post.post_id)。
  2. 控制结果集大小 :添加 LIMIT 或筛选条件。
  3. 使用 STRAIGHT_JOIN:手动指定连接顺序(避免优化器选错计划)。
相关推荐
t198751283 小时前
解决MySQL删除/var/lib/mysql下的所有文件后无法启动的问题
数据库·mysql·adb
IT北辰6 小时前
用Python+MySQL实战解锁企业财务数据分析
python·mysql·数据分析
AWS官方合作商9 小时前
Amazon RDS for MySQL成本优化:RDS缓存降本实战
数据库·mysql·aws
程序猿小D11 小时前
Java项目:基于SSM框架实现的校园活动资讯网管理系统【ssm+B/S架构+源码+数据库+毕业论文+远程部署】
java·数据库·mysql·spring·毕业设计·ssm框架·校园活动
__風__15 小时前
从本地 Docker 部署的 Dify 中导出知识库内容(1.6版本亲测有效)
人工智能·python·mysql·语言模型
Aeside115 小时前
从订单ID说起:揭秘MySQL索引结构 & 设计
mysql
知其然亦知其所以然16 小时前
MySQL社招面试题:索引有哪几种类型?我讲给你听的不只是答案!
后端·mysql·面试
Lx35216 小时前
子查询扁平化技巧:减少嵌套层级的查询重构
sql·mysql
神仙别闹17 小时前
基于JSP+MySQL 实现(Web)毕业设计题目收集系统
java·前端·mysql
正经教主17 小时前
【n8n】mysql凭证设置,及注意问题
数据库·mysql·n8n