MySQL 性能优化:慢查询与索引优化实战

MySQL 性能优化:慢查询与索引优化实战

性能优化是 DBA 和开发者的核心技能。本文从慢查询分析出发,深入讲解 EXPLAIN 执行计划解读、索引失效场景、覆盖索引优化、ICP 索引条件下推,以及生产环境的性能优化实战技巧。

一、慢查询分析

1.1 开启慢查询日志

sql 复制代码
-- 查看慢查询配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL slow_query_log_file = '/var/lib/mysql/slow.log';
SET GLOBAL long_query_time = 1;  -- 超过1秒记录
SET GLOBAL log_queries_not_using_indexes = 'ON';  -- 记录未使用索引的查询

1.2 my.cnf 配置

ini 复制代码
[mysqld]
# 慢查询日志
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1

# 通用日志(调试用)
general_log = 0
general_log_file = /var/lib/mysql/general.log

# 性能模式
performance_schema = ON

1.3 慢查询日志分析

bash 复制代码
# 使用 mysqldumpslow 分析
mysqldumpslow -s t -t 10 /var/lib/mysql/slow.log

# 参数说明:
# -s: 排序方式 (c=次数, t=时间, l=锁时间, r=返回行数)
# -t: 显示前 N 条
# -g: 正则匹配

# 常用命令
mysqldumpslow -s t -t 10 slow.log           # 按时间排序前10
mysqldumpslow -s c -t 10 slow.log            # 按次数排序前10
mysqldumpslow -s r -t 10 slow.log            # 按返回行数排序前10

1.4 pt-query-digest 分析

bash 复制代码
# 安装 Percona Toolkit
# apt install percona-toolkit

# 分析慢查询
pt-query-digest slow.log

# 输出示例:
# Row 1: 0.053s  UPDATE orders SET status='completed' WHERE order_id=12345
# Row 2: 0.089s  SELECT * FROM users WHERE email='test@example.com'

1.5 performance_schema

sql 复制代码
-- 开启监控
UPDATE performance_schema.setup_instruments 
SET ENABLED = 'YES' 
WHERE NAME LIKE 'statement/%';

UPDATE performance_schema.setup_consumers 
SET ENABLED = 'YES' 
WHERE NAME LIKE 'events_statements_%';

-- 查看慢查询
SELECT 
    DIGEST_TEXT AS query,
    COUNT_STAR AS exec_count,
    SUM_TIMER_WAIT/1000000000000 AS total_time,
    AVG_TIMER_WAIT/1000000000000 AS avg_time,
    SUM_ROWS_EXAMINED AS rows_scanned
FROM performance_schema.events_statements_summary_by_digest
WHERE DIGEST_TEXT LIKE '%SELECT%'
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

二、EXPLAIN 执行计划

2.1 EXPLAIN 输出字段

sql 复制代码
EXPLAIN SELECT * FROM users WHERE name = '张三';

EXPLAIN字段
EXPLAIN 输出
id
select_type
table
type
possible_keys
key
key_len
ref
rows
Extra

2.2 type 字段详解

type 值 含义 性能
system 表只有一行 最好
const 主键或唯一索引等值查询 极好
eq_ref 关联时使用主键或唯一索引
ref 非唯一索引等值查询
range 索引范围扫描 中等
index 全索引扫描 较差
ALL 全表扫描 最差
sql 复制代码
-- system
EXPLAIN SELECT * FROM (SELECT * FROM t WHERE id=1) AS tmp;

-- const
EXPLAIN SELECT * FROM t WHERE id = 1;  -- 主键查询

-- eq_ref
EXPLAIN SELECT * FROM t1 JOIN t2 ON t1.id = t2.id;  -- 关联主键

-- ref
EXPLAIN SELECT * FROM t WHERE name = '张三';  -- 普通索引

-- range
EXPLAIN SELECT * FROM t WHERE id > 10;  -- 范围查询

-- index
EXPLAIN SELECT COUNT(*) FROM t;  -- 全索引扫描

-- ALL
EXPLAIN SELECT * FROM t WHERE name LIKE '%张%';  -- 全表扫描

2.3 key_len 计算

sql 复制代码
-- key_len 表示索引使用的字节数
-- 计算公式:
-- int: 4 bytes
-- bigint: 8 bytes
-- varchar(n): n * 3 (utf8mb4) + 2 (长度) + 1 (nullable)
-- char(n): n * 3 (utf8mb4)

-- 示例
CREATE TABLE t (
    id BIGINT,                    -- 8 bytes
    name VARCHAR(50),              -- 50 * 3 + 2 = 152 bytes
    age INT,                       -- 4 bytes
    INDEX idx (name, age)
);

EXPLAIN SELECT * FROM t WHERE name = '张三' AND age = 25;
-- key_len = 152 + 4 = 156 bytes

2.4 Extra 字段分析

sql 复制代码
-- Using index: 覆盖索引,不需要回表
EXPLAIN SELECT name, age FROM t WHERE name = '张三';

-- Using where: 使用 WHERE 过滤
EXPLAIN SELECT * FROM t WHERE name = '张三';

-- Using index condition: 索引条件下推 (ICP)
EXPLAIN SELECT * FROM t WHERE name LIKE '张%' AND age > 25;

-- Using MRR: 多范围读,优化磁盘 IO
EXPLAIN SELECT * FROM t WHERE id IN (1, 5, 10);

-- Using filesort: 需要额外排序
EXPLAIN SELECT * FROM t ORDER BY create_time;

-- Using temporary: 使用临时表
EXPLAIN SELECT DISTINCT name FROM t;

-- Range checked for each record: 范围检查
EXPLAIN SELECT * FROM t1, t2 WHERE t1.id > t2.id;

三、索引失效场景

3.1 索引失效一览

索引失效场景
函数操作
WHERE YEAR(create_time) = 2024
隐式类型转换
phone = 13800138000 (phone 是 VARCHAR)
LIKE 前缀通配符
WHERE name LIKE '%张%'
OR 条件
WHERE name = '张三' OR age = 25
NOT 操作
WHERE age != 25
WHERE age NOT IN (20, 25)
范围查询在中间
索引 (a, b, c),查询 b > 5

3.2 函数导致索引失效

sql 复制代码
-- ❌ 索引失效
SELECT * FROM orders 
WHERE YEAR(create_time) = 2024 
  AND MONTH(create_time) = 6;

-- ✅ 优化方案1: 使用范围查询
SELECT * FROM orders 
WHERE create_time >= '2024-06-01' 
  AND create_time < '2024-07-01';

-- ✅ 优化方案2: 创建函数索引 (MySQL 8.0+)
CREATE INDEX idx_year_month ON orders ((YEAR(create_time)), (MONTH(create_time)));

-- ❌ 索引失效
SELECT * FROM users 
WHERE SUBSTRING(phone, 1, 3) = '138';

-- ✅ 优化方案
SELECT * FROM users WHERE phone LIKE '138%';

3.3 隐式类型转换

sql 复制代码
-- phone 是 VARCHAR(20) 类型
-- 查询传入整数,MySQL 会将字符串转为整数
-- 导致索引失效

-- ❌ 索引失效
SELECT * FROM users WHERE phone = 13800138000;

-- ✅ 正确写法
SELECT * FROM users WHERE phone = '13800138000';

-- 如果无法控制参数类型,使用 CAST
SELECT * FROM users WHERE CAST(phone AS CHAR) = '13800138000';

3.4 OR 条件导致索引失效

sql 复制代码
-- ❌ OR 导致全表扫描
SELECT * FROM users WHERE name = '张三' OR age = 25;
-- age 列没有索引,导致全表扫描

-- ✅ 优化方案1: 使用 UNION
SELECT * FROM users WHERE name = '张三'
UNION
SELECT * FROM users WHERE age = 25;

-- ✅ 优化方案2: 给 age 添加索引
CREATE INDEX idx_age ON users(age);

-- ✅ 优化方案3: 使用 IN
SELECT * FROM users WHERE name IN ('张三', (SELECT name FROM users WHERE age = 25));

3.5 联合索引失效

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

-- ✅ 完全使用索引
SELECT * FROM users WHERE name = '张三' AND age = 25 AND city = '北京';
SELECT * FROM users WHERE name = '张三' AND age = 25;
SELECT * FROM users WHERE name = '张三';

-- ❌ 完全不使用索引
SELECT * FROM users WHERE age = 25;
SELECT * FROM users WHERE city = '北京';
SELECT * FROM users WHERE age = 25 AND city = '北京';

-- ⚠️ 部分使用索引
SELECT * FROM users WHERE name = '张三' AND city = '北京';
-- 使用 name 部分,city 需要回表过滤

-- ⚠️ age 范围查询,后面的索引失效
SELECT * FROM users WHERE name = '张三' AND age > 25 AND city = '北京';
-- 使用 name 部分,age 和 city 需要回表过滤

四、索引优化技巧

4.1 覆盖索引

覆盖索引
SELECT name, age FROM users WHERE name='张三'
索引 (name, age) 已包含所需字段
无需回表
只需 1 次 IO
非覆盖索引
SELECT * FROM users WHERE name='张三'
命中 name 索引
获取主键
回表查询聚簇索引
1-2 次额外 IO

4.2 索引下推 (ICP)

sql 复制代码
-- 索引 (name, age, city)
-- MySQL 5.6+ 支持 ICP

-- 优化前 (不启用 ICP)
SELECT * FROM users WHERE name LIKE '张%' AND age = 25;

-- 不启用 ICP:
-- 1. 使用 name 索引找到所有 name LIKE '张%' 的记录
-- 2. 回表获取完整行
-- 3. 在 Server 层过滤 age = 25

-- 启用 ICP:
-- 1. 使用 name 索引找到所有 name LIKE '张%' 的记录
-- 2. 在 Storage 层过滤 age = 25 (Index Condition Pushdown)
-- 3. 只回表获取符合条件的记录

EXPLAIN 输出: Using index condition (而不是 Using where)

4.3 MRR 优化

sql 复制代码
-- MRR (Multi-Range Read) 优化
-- 适用于范围查询和 JOIN

-- 优化前
SELECT * FROM orders WHERE id IN (1, 5, 10, 20, 15, 3);

-- 不启用 MRR: 按 id 顺序回表查询,可能随机 IO

-- 启用 MRR:
-- 1. 先获取主键列表 [1, 3, 5, 10, 15, 20]
-- 2. 对主键排序 [1, 3, 5, 10, 15, 20]
-- 3. 按排序后的顺序回表查询,顺序 IO

SET @@optimizer_switch = 'mrr=on,mrr_cost_based=off';  -- 强制启用
EXPLAIN 输出: Using MRR

4.4 前缀索引

sql 复制代码
-- 对长字段创建前缀索引
ALTER TABLE orders ADD INDEX idx_order_no (order_no(10));

-- 选择合适的前缀长度
SELECT 
    COUNT(DISTINCT LEFT(order_no, 5)) / COUNT(*) AS sel5,
    COUNT(DISTINCT LEFT(order_no, 10)) / COUNT(*) AS sel10,
    COUNT(DISTINCT LEFT(order_no, 15)) / COUNT(*) AS sel15,
    COUNT(DISTINCT order_no) / COUNT(*) AS sel_all
FROM orders;

-- 选择 sel 接近 sel_all 的最小长度

4.5 索引排序优化

sql 复制代码
-- 索引用于排序
CREATE INDEX idx_create_time ON orders (create_time);

-- ✅ 可以利用索引排序
SELECT * FROM orders ORDER BY create_time;
SELECT * FROM orders WHERE status = 1 ORDER BY create_time;

-- ❌ 无法利用索引排序 (filesort)
SELECT * FROM orders ORDER BY -create_time;  -- 使用表达式
SELECT * FROM orders WHERE status = 1 ORDER BY create_time, id;
-- status 不是等值查询,无法利用索引

-- ✅ 复合索引用于排序
CREATE INDEX idx_status_create ON orders (status, create_time);

-- ✅ 利用复合索引排序
SELECT * FROM orders WHERE status = 1 ORDER BY create_time;

五、实战优化案例

5.1 分页优化

sql 复制代码
-- ❌ 低效分页
SELECT * FROM orders ORDER BY id LIMIT 1000000, 10;
-- 需要扫描 1000010 行

-- ✅ 优化方案1: 延迟关联
SELECT * FROM orders 
INNER JOIN (
    SELECT id FROM orders ORDER BY id LIMIT 1000000, 10
) AS t USING(id);

-- ✅ 优化方案2: 记录上次位置
-- 第一次查询
SELECT * FROM orders ORDER BY id LIMIT 10;
-- 返回 last_id = 100

-- 后续查询
SELECT * FROM orders 
WHERE id > 100 
ORDER BY id LIMIT 10;

-- ✅ 优化方案3: 使用主键代替 OFFSET
SELECT * FROM orders 
WHERE id > (
    SELECT id FROM orders LIMIT 1000000, 1
)
LIMIT 10;

5.2 COUNT 优化

sql 复制代码
-- ❌ 低效 COUNT
SELECT COUNT(*) FROM orders WHERE status = 1;
-- 全表扫描

-- ✅ 优化方案1: 使用覆盖索引
SELECT COUNT(*) FROM orders WHERE status = 1;
-- 创建索引 (status),覆盖查询

-- ✅ 优化方案2: 使用 EXPLAIN 估算
EXPLAIN SELECT * FROM orders WHERE status = 1;
-- rows 列是估算的行数

-- ✅ 优化方案3: 使用统计表
CREATE TABLE order_stats AS
SELECT status, COUNT(*) AS cnt FROM orders GROUP BY status;

5.3 JOIN 优化

sql 复制代码
-- 小表驱动大表
-- EXPLAIN 显示的 rows 是估算值,值小的表应作为驱动表

-- ❌ 低效 JOIN
SELECT * FROM orders o
INNER JOIN users u ON o.user_id = u.id
WHERE u.city = '北京';
-- users 表过滤后可能还有很多记录

-- ✅ 优化方案1: 确保过滤条件在驱动表上
SELECT * FROM users u
INNER JOIN orders o ON o.user_id = u.id
WHERE u.city = '北京';
-- users 先过滤,结果集更小

-- ✅ 优化方案2: 添加合适索引
CREATE INDEX idx_user_city ON users(city);
CREATE INDEX idx_order_user ON orders(user_id);

-- ✅ 优化方案3: 使用 STRAIGHT_JOIN 强制顺序
SELECT STRAIGHT_JOIN * FROM users u
INNER JOIN orders o ON o.user_id = u.id
WHERE u.city = '北京';

5.4 子查询优化

sql 复制代码
-- ❌ 低效子查询
SELECT * FROM users 
WHERE id IN (
    SELECT user_id FROM orders WHERE amount > 1000
);

-- MySQL 优化器可能将 IN 转为 EXISTS
-- 可能全表扫描

-- ✅ 优化方案1: 改为 JOIN
SELECT DISTINCT u.* FROM users u
INNER JOIN orders o ON o.user_id = u.id
WHERE o.amount > 1000;

-- ✅ 优化方案2: 使用索引
CREATE INDEX idx_order_amount ON orders(amount, user_id);
-- 覆盖查询,不需要回表

-- ✅ 优化方案3: 提前聚合
SELECT u.* FROM users u
INNER JOIN (
    SELECT user_id FROM orders 
    WHERE amount > 1000 
    GROUP BY user_id
) o ON o.user_id = u.id;

六、Optimizer 优化器深度解析

6.1 MySQL 查询优化器原理

优化器决策因素
代价估算
扫描行数
索引深度
排序代价
JOIN 顺序
查询优化器工作流程
SQL 语句

  1. 解析 SQL
  2. 生成 AST
  3. 预处理 (验证、权限)
  4. 查询优化
  5. 生成执行计划
  6. 执行计划

6.2 执行计划字段详解

重要字段
id: 查询序号
select_type: 查询类型
table: 涉及哪个表
partitions: 涉及哪个分区
type: 访问类型
possible_keys: 可能使用的索引
key: 实际使用的索引
key_len: 索引长度
ref: 索引比较的列
rows: 估算扫描行数
filtered: 过滤后百分比
Extra: 额外信息
EXPLAIN FORMAT=JSON 输出
cost_info
query_cost: 查询总代价
read_cost: 读取代价
eval_cost: 计算代价
nested_loop: NLJ 代价

6.3 optimizer_trace 分析

sql 复制代码
-- 开启 optimizer_trace
SET optimizer_trace = 'enabled=on';
SET optimizer_trace_max_mem_size = 1048576;

-- 执行查询
SELECT * FROM orders WHERE status = 1 AND create_time > '2024-01-01';

-- 查看优化器决策
SELECT * FROM information_schema.OPTIMIZER_TRACE;

-- 关闭
SET optimizer_trace = 'enabled=off';
json 复制代码
{
  "join_optimization": {
    "considered_execution_plans": [
      {
        "plan_prefix": [],
        "table": "`orders`",
        "best_access_path": {
          "access_type": "ref",
          "key": "idx_status",
          "rows": 1000,
          "cost": 1200
        }
      }
    ]
  }
}

6.4 Hint 用法大全

sql 复制代码
-- 强制使用索引
SELECT * FROM orders USE INDEX (idx_status) WHERE status = 1;

-- 忽略索引
SELECT * FROM orders IGNORE INDEX (idx_status) WHERE status = 1;

-- 强制使用指定索引
SELECT * FROM orders FORCE INDEX (idx_status) WHERE status = 1;

-- 强制使用 JOIN 顺序
SELECT STRAIGHT_JOIN * FROM orders o
INNER JOIN users u ON o.user_id = u.id;

-- 改变优化器策略
SELECT /*+ SET_VAR(optimizer_switch='index_merge=off') */ * FROM orders;

-- 改变成本模型
SELECT /*+ NO_ICP(t) */ * FROM orders t WHERE status = 1 AND amount > 100;

-- 强制 MRR
SELECT /*+ MRR(orders) */ * FROM orders WHERE id IN (1, 2, 3);

6.5 成本模型详解

索引成本计算
使用索引的成本
索引扫描成本
回表成本 (每行一次 IO)
比较成本 (每行一次)
成本因子
总成本 = Σ(行数 × 单行成本) + Σ(IO成本)
IO成本: 读取页面数 × 页面读取成本
页面读取成本 = 1 (磁盘) / 0.1 (SSD)
CPU成本: 行处理成本 × 行数

6.6 索引条件下推 (ICP) 深度

ICP 开启条件
ICP 开启条件
MySQL 5.6+
使用 InnoDB 或 MyISAM
非唯一索引
WHERE 条件可下推
ICP 执行流程
查询: SELECT * FROM t WHERE name LIKE '张%' AND age > 25
Server 层发送查询到 Storage 层
Storage 层使用 name 索引
Storage 层过滤 age > 25 (ICP)
只回表获取符合 age > 25 的行


六·续、性能剖析与实战

6.7 Performance Schema 深度使用

sql 复制代码
-- 启用所有监控
UPDATE performance_schema.setup_instruments
SET ENABLED = 'YES', TIMED = 'YES'
WHERE NAME LIKE '%';

UPDATE performance_schema.setup_consumers
SET ENABLED = 'YES';

-- 监控语句延迟
SELECT 
    DIGEST,
    DIGEST_TEXT,
    COUNT_STAR,
    SUM_TIMER_WAIT / 1000000000000 AS total_sec,
    AVG_TIMER_WAIT / 1000000000000 AS avg_sec,
    MIN_TIMER_WAIT / 1000000000000 AS min_sec,
    MAX_TIMER_WAIT / 1000000000000 AS max_sec
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

-- 监控表级 IO
SELECT 
    OBJECT_SCHEMA,
    OBJECT_NAME,
    COUNT_FETCH,
    COUNT_INSERT,
    COUNT_UPDATE,
    COUNT_DELETE
FROM performance_schema.table_io_waits_summary_by_table
WHERE OBJECT_SCHEMA = 'myapp'
ORDER BY SUM_TIMER_WAIT DESC;

-- 监控索引使用
SELECT 
    OBJECT_SCHEMA,
    OBJECT_NAME,
    INDEX_NAME,
    COUNT_FETCH,
    COUNT_INSERT,
    COUNT_UPDATE,
    COUNT_DELETE,
    SUM_TIMER_WAIT as total_wait
FROM performance_schema.table_lock_waits_summary_by_table
WHERE OBJECT_SCHEMA = 'myapp';

-- 监控连接
SELECT 
    USER,
    COUNT_CONNECTIONS,
    CURRENT_CONNECTIONS,
    TOTAL_CONNECTIONS
FROM performance_schema.accounts
ORDER BY TOTAL_CONNECTIONS DESC;

6.8 慢查询优化案例

sql 复制代码
-- 原始慢查询
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 = 1 
  AND o.create_time >= '2024-01-01'
  AND o.create_time < '2024-02-01';

-- Step 1: EXPLAIN 分析
EXPLAIN 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 = 1 
  AND o.create_time >= '2024-01-01'
  AND o.create_time < '2024-02-01';
  
-- 问题: type=ALL, rows=1000000

-- Step 2: 添加合适索引
CREATE INDEX idx_order_status_time ON orders(status, create_time);
CREATE INDEX idx_order_user ON orders(user_id);

-- Step 3: 再次分析
EXPLAIN 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 = 1 
  AND o.create_time >= '2024-01-01'
  AND o.create_time < '2024-02-01';

-- 优化后: type=range, rows=100, 使用了覆盖索引

-- Step 4: 使用覆盖索引避免回表
SELECT o.id, o.order_no, u.name, u.phone
FROM orders o
INNER JOIN users u USE INDEX(idx_order_user) ON o.user_id = u.id
WHERE o.status = 1 
  AND o.create_time >= '2024-01-01'
  AND o.create_time < '2024-02-01';

6.9 连接池优化

java 复制代码
// HikariCP 核心参数调优
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/myapp");
config.setUsername("root");
config.setPassword("password");

// 连接池大小
config.setMaximumPoolSize(20);    // CPU 核心数 * 2
config.setMinimumIdle(5);        // 最小空闲连接

// 超时设置
config.setConnectionTimeout(30000);  // 获取连接超时 30s
config.setIdleTimeout(600000);       // 空闲连接超时 10min
config.setMaxLifetime(1800000);      // 连接最大生命周期 30min

// 性能优化
config.setAutoCommit(true);
config.setCachePrepStmts(true);           // 开启预处理语句缓存
config.setPrepStmtCacheSize(250);         // 缓存大小
config.setPrepStmtCacheSqlLimit(2048);     // 最大 SQL 长度
config.setUseServerPrepStmts(true);       // 服务端预处理
config.setRewriteBatchedStatements(true); // 批量写入

// 监控
config.setRegisterMbeans(true);
config.setPoolName("MyAppPool");

六、面试高频问题

6.1 如何分析 SQL 性能?

复制代码
1. 开启慢查询日志
   - 设置 long_query_time
   - 记录未使用索引的查询

2. 使用 EXPLAIN 分析执行计划
   - type: 优先 const, eq_ref, ref
   - key: 确认使用了索引
   - rows: 扫描行数越少越好
   - Extra: 避免 Using filesort, Using temporary

3. 使用 PROFILING
   - SHOW PROFILES;
   - 查看每个阶段的耗时

4. 使用 optimizer_trace
   - 分析优化器决策过程

5. 使用 Performance Schema
   - 实时监控查询性能

6.2 什么是覆盖索引?

复制代码
覆盖索引是一种优化手段:

定义:索引中包含查询需要的所有字段

示例:
索引 (name, age) 上的查询:
  SELECT name, age FROM users WHERE name = '张三';

优势:
- 只需扫描索引,不需要回表
- 减少 IO 操作
- 提高查询性能

验证方法:
  EXPLAIN 输出中 Extra 列显示 "Using index"

6.3 什么是索引条件下推?

复制代码
ICP (Index Condition Pushdown) 是 MySQL 5.6+ 的优化:

原理:
- 将 WHERE 条件下推到存储引擎层
- 在索引层面过滤数据
- 减少回表次数

示例:
索引 (name, age),查询:
  SELECT * FROM users WHERE name LIKE '张%' AND age > 25;

无 ICP:
  1. 使用 name 索引找到所有 '张%' 的记录
  2. 回表获取完整行
  3. 在 Server 层过滤 age > 25

有 ICP:
  1. 使用 name 索引找到所有 '张%' 的记录
  2. 在 Storage 层过滤 age > 25 (ICP)
  3. 只回表获取符合 age > 25 的记录

验证方法:
  EXPLAIN 输出中 Extra 列显示 "Using index condition"

6.4 如何优化分页查询?

复制代码
分页查询的性能问题:
- OFFSET 很大时,需要扫描大量行
- LIMIT 1000000, 10 扫描 1000010 行

优化方案:

1. 延迟关联
   SELECT * FROM orders 
   INNER JOIN (
     SELECT id FROM orders ORDER BY id LIMIT 1000000, 10
   ) t USING(id);

2. 记录上次位置
   - 第一次: LIMIT 10,返回 last_id
   - 后续: WHERE id > last_id LIMIT 10

3. 使用书签
   - 记录每页的起始 ID
   - 使用 WHERE id > start_id LIMIT 10

4. 使用游标
   - 使用上一页的最后一行的某个字段
   - WHERE (age, id) > (25, 100) LIMIT 10

6.5 什么情况下索引会失效?

复制代码
1. 函数操作
   WHERE YEAR(create_time) = 2024
   WHERE LENGTH(phone) = 11

2. 隐式类型转换
   phone VARCHAR,但传入整数
   WHERE phone = 13800138000

3. LIKE 前缀通配符
   WHERE name LIKE '%张%'
   WHERE name LIKE '%张'

4. OR 连接非索引列
   WHERE name = '张三' OR age = 25
   (age 没有索引)

5. NOT 操作
   WHERE age != 25
   WHERE age NOT IN (20, 25)

6. 联合索引跳跃
   索引 (a, b, c)
   查询 WHERE b = 1 (跳过 a)

7. 范围查询在中间
   索引 (a, b, c)
   查询 WHERE a = 1 AND c = 1 (跳过 b)

七、总结

7.1 优化检查清单

复制代码
✅ SQL 优化检查清单:

1. 执行计划分析
   - type 是否达到 range 以上
   - key 是否使用索引
   - Extra 是否没有 Using filesort

2. 索引使用
   - 是否需要回表
   - 是否可以使用覆盖索引
   - 是否存在索引失效

3. 慢查询
   - 是否开启慢查询日志
   - 定期分析慢查询日志
   - 优化高频慢查询

4. 监控
   - 监控 Query 响应时间
   - 监控索引使用情况
   - 监控锁等待

7.2 优化口诀

复制代码
SQL 优化口诀:

全表扫描要避免,索引列上无函数
隐式转换要小心,OR 条件需谨慎
LIKE 百分号在前,排序分页要优化
覆盖索引最理想,查询字段要精简
JOIN 小表来驱动,子查询转 JOIN
EXPLAIN 来看执行计划,性能问题早发现

7.3 性能优化优先级

复制代码
优化优先级(从高到低):

1. SQL 和索引优化 (效果最明显)
   - 改写 SQL
   - 创建合适索引

2. 架构优化 (需要较大改动)
   - 读写分离
   - 分库分表
   - 缓存

3. 服务器优化 (效果有限)
   - 硬件升级
   - 参数调优
   - 系统配置
相关推荐
南境十里·墨染春水1 小时前
C++ 日志 4—— 多线程安全与异步日志优化
数据库·c++·安全
旧物有情1 小时前
Unity性能优化之合批,什么是合批?
unity·性能优化·游戏引擎
七夜zippoe1 小时前
DolphinDB索引设计:提升查询性能
数据库·索引·性能·查询·dolphindb
2401_898717661 小时前
HTML5中SVG原生动画标签Animate的基础用法
jvm·数据库·python
小江的记录本1 小时前
【MySQL】《MySQL基础架构 面试核心考点问答清单》
前端·数据库·后端·sql·mysql·adb·面试
猫的玖月1 小时前
(七)函数
android·数据库·sql
2401_867623981 小时前
mysql如何导出特定条件的查询数据_使用mysqldump加where参数
jvm·数据库·python
会编程的土豆1 小时前
MySQL 窗口函数详解
数据库·后端·mysql
武帝为此2 小时前
【软件开发日志介绍】
java·前端·数据库