agentleFS
huanglei288766huanglei288766/claude-code-zh/skills/mysql-optimization/SKILL.md

mysql-optimization

Skill6 starsChanged 8 months agoMITView on GitHub

本 Skill 涵盖 MySQL 8.0 的查询优化、索引设计、表结构设计规范,帮助 Claude Code 给出高性能的数据库方案。 适用场景: - 新建表时设计索引策略 - 慢查询分析与优化 - 大表分页查询优化 - 联合查询性能调优

The file

Files
SKILL.md

MySQL 查询优化与索引设计

概述

本 Skill 涵盖 MySQL 8.0 的查询优化、索引设计、表结构设计规范,帮助 Claude Code 给出高性能的数据库方案。

适用场景:

  • 新建表时设计索引策略
  • 慢查询分析与优化
  • 大表分页查询优化
  • 联合查询性能调优

索引设计原则

最左前缀原则

-- 联合索引 (a, b, c),以下查询能否用到索引:
-- ✅ WHERE a = 1               → 用到 a
-- ✅ WHERE a = 1 AND b = 2     → 用到 (a, b)
-- ✅ WHERE a = 1 AND b > 2     → 用到 (a, b),b 是范围条件后续列失效
-- ❌ WHERE b = 2               → 跳过 a,无法使用
-- ❌ WHERE b = 2 AND c = 3     → 跳过 a,无法使用

-- 正确建立联合索引:高选择性列在前,等值条件列在前,范围条件列在后
ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, created_at);
-- 支持:WHERE user_id = ? AND status = ? AND created_at > ?

索引覆盖(避免回表)

-- ❌ 需要回表(idx_user_id 只有 user_id,还需要查主键取其他列)
SELECT id, order_no, amount FROM orders WHERE user_id = 123;

-- ✅ 覆盖索引(查询所需列都在索引里)
ALTER TABLE orders ADD INDEX idx_user_cover (user_id, id, order_no, amount);
-- EXPLAIN 中 Extra 显示 "Using index"

索引失效场景

-- ❌ 函数运算导致索引失效
SELECT * FROM orders WHERE DATE(created_at) = '2026-03-14';
-- ✅ 改写为范围查询
SELECT * FROM orders
WHERE created_at >= '2026-03-14 00:00:00'
  AND created_at <  '2026-03-15 00:00:00';

-- ❌ 隐式类型转换(user_id 是 varchar,传入数字)
SELECT * FROM orders WHERE user_id = 123456;
-- ✅ 类型匹配
SELECT * FROM orders WHERE user_id = '123456';

-- ❌ LIKE 以通配符开头
SELECT * FROM users WHERE name LIKE '%张%';
-- ✅ 改用全文索引或 ES

大表分页优化

-- ❌ 深分页性能极差(OFFSET 100万需要扫描100万行再丢弃)
SELECT * FROM orders ORDER BY id LIMIT 1000000, 20;

-- ✅ 方案一:游标分页(推荐,需要业务支持)
-- 第一页
SELECT * FROM orders WHERE id > 0 ORDER BY id LIMIT 20;
-- 下一页(传入上一页最后一条 id)
SELECT * FROM orders WHERE id > #{lastId} ORDER BY id LIMIT 20;

-- ✅ 方案二:子查询 + 覆盖索引
SELECT o.* FROM orders o
JOIN (
  SELECT id FROM orders ORDER BY id LIMIT 1000000, 20
) t ON o.id = t.id;

-- ✅ 方案三:业务限制(禁止跳页,只允许"下一页")

常见慢查询模式与优化

N+1 查询

// ❌ N+1:查 100 个订单,再循环查 100 次用户
List<Order> orders = orderMapper.findAll();
for (Order order : orders) {
    User user = userMapper.findById(order.getUserId()); // N 次查询
    order.setUserName(user.getName());
}

// ✅ 批量查询 + Map 组装
List<Order> orders = orderMapper.findAll();
Set<Long> userIds = orders.stream().map(Order::getUserId).collect(toSet());
Map<Long, User> userMap = userMapper.findByIds(userIds).stream()
    .collect(toMap(User::getId, identity()));
orders.forEach(o -> o.setUserName(userMap.get(o.getUserId()).getName()));

联表查询 vs 多次单表

-- 数据量小(< 100万):JOIN 性能好
SELECT o.*, u.name AS user_name
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 1;

-- 数据量大(> 百万):分别查询再应用层组装,避免大表 JOIN
-- 1. SELECT * FROM orders WHERE status = 1 LIMIT 100
-- 2. 取出 user_ids
-- 3. SELECT * FROM users WHERE id IN (...)

表结构设计规范

CREATE TABLE orders (
    -- 主键:BIGINT UNSIGNED AUTO_INCREMENT(不用 UUID,索引性能差)
    id          BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,

    -- 业务 ID:UUID v7 或雪花算法(对外暴露,不暴露自增 id)
    order_no    VARCHAR(32)     NOT NULL,

    -- 外键字段:不加外键约束(分布式/高并发场景)
    user_id     BIGINT UNSIGNED NOT NULL,

    -- 金额:DECIMAL,不用 FLOAT/DOUBLE(浮点精度问题)
    amount      DECIMAL(12, 2)  NOT NULL DEFAULT '0.00',

    -- 枚举状态:TINYINT(节省空间,代码里用常量映射)
    status      TINYINT         NOT NULL DEFAULT 0,

    -- 时间:DATETIME(3) 精确到毫秒,存 UTC
    created_at  DATETIME(3)     NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    updated_at  DATETIME(3)     NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
                                         ON UPDATE CURRENT_TIMESTAMP(3),
    -- 逻辑删除
    deleted     TINYINT(1)      NOT NULL DEFAULT 0,

    PRIMARY KEY (id),
    UNIQUE KEY uk_order_no (order_no),
    KEY idx_user_status (user_id, status),
    KEY idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

EXPLAIN 解读

EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND status = 1;
字段好的值警告值含义
typeconst/ref/rangeindex/ALL扫描类型,ALL 是全表扫描
key索引名NULL实际使用的索引
rows越小越好万级以上需优化估计扫描行数
ExtraUsing indexUsing filesort / Using temporary附加信息

优化目标: type 至少 range,避免 ALL;Extra 不出现 Using filesort(无索引排序)


MyBatis-Plus 常见优化

// ❌ 查询所有字段
orderMapper.selectList(new LambdaQueryWrapper<Order>()
    .eq(Order::getUserId, userId));

// ✅ 只查需要的字段
orderMapper.selectList(new LambdaQueryWrapper<Order>()
    .select(Order::getId, Order::getOrderNo, Order::getStatus)
    .eq(Order::getUserId, userId)
    .eq(Order::getDeleted, 0));

// ✅ 大批量操作用 saveBatch,不要循环 insert
orderMapper.insertBatchSomeColumn(orderList);  // MyBatis-Plus 批量插入

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.