SQL优化与索引策略实战指南

一、SQL 优化基本原则

1. 基础优化原则

  • 避免 SELECT *,只选择需要的列

  • 使用 LIMIT 限制返回的行数

  • 避免在 WHERE 子句中使用函数或表达式

  • 合理使用连接(JOIN)代替子查询

二、索引策略示例

1. 单列索引

sql

复制代码
-- 创建单列索引
CREATE INDEX idx_user_email ON users(email);
CREATE INDEX idx_order_date ON orders(order_date);

-- 复合索引(最左前缀原则)
CREATE INDEX idx_user_name_email ON users(last_name, first_name, email);

2. 覆盖索引优化

sql

复制代码
-- 创建覆盖索引,包含查询中所有需要的列
CREATE INDEX idx_order_covering ON orders(user_id, order_date, total_amount, status);

-- 查询可以利用覆盖索引,无需回表
SELECT user_id, order_date, total_amount 
FROM orders 
WHERE user_id = 123 AND order_date > '2024-01-01';

3. 索引失效的常见场景

sql

复制代码
-- 1. 对索引列进行运算
SELECT * FROM users WHERE YEAR(create_time) = 2024; -- 索引失效
SELECT * FROM users WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01'; -- 有效

-- 2. 使用 NOT LIKE,!=,<> 
SELECT * FROM users WHERE email NOT LIKE 'test%@example.com'; -- 可能失效

-- 3. OR 条件未全部使用索引
SELECT * FROM users WHERE email = 'a@b.com' OR phone = '123456'; -- 如果phone无索引,索引可能失效

三、查询优化案例与 Explain 对比

案例1:分页查询优化

优化前:

sql

复制代码
EXPLAIN SELECT * FROM orders 
ORDER BY create_time DESC 
LIMIT 100000, 20;

-- Explain 结果:
-- type: ALL (全表扫描)
-- rows: 100020
-- Extra: Using filesort

优化后:

sql

复制代码
EXPLAIN SELECT * FROM orders 
WHERE id > (
    SELECT id FROM orders 
    ORDER BY create_time DESC 
    LIMIT 100000, 1
) 
ORDER BY create_time DESC 
LIMIT 20;

-- 或者使用延迟关联
EXPLAIN SELECT o.* FROM orders o
JOIN (
    SELECT id FROM orders 
    ORDER BY create_time DESC 
    LIMIT 100000, 20
) AS tmp ON o.id = tmp.id;

-- Explain 结果:
-- type: range (范围扫描)
-- rows: 20
-- Extra: Using where

案例2:JOIN 查询优化

优化前:

sql

复制代码
EXPLAIN SELECT * 
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE o.status = 'completed' 
AND u.country = 'US';

-- Explain 结果可能显示:
-- 驱动表选择不当
-- 未使用合适的索引

优化后:

sql

复制代码
-- 1. 添加复合索引
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
CREATE INDEX idx_users_country ON users(country);

-- 2. 重写查询
EXPLAIN SELECT o.*, u.name 
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.country = 'US' 
AND o.status = 'completed';

-- Explain 结果改善:
-- 驱动表变为 users(更小的结果集)
-- 使用了复合索引

案例3:子查询优化

优化前:

sql

复制代码
EXPLAIN SELECT * FROM products p
WHERE p.category_id IN (
    SELECT category_id FROM popular_categories 
    WHERE sales > 10000
);

-- Explain 结果:
-- type: DEPENDENT SUBQUERY
-- Extra: Using where

优化后:

sql

复制代码
-- 使用 JOIN 代替子查询
EXPLAIN SELECT DISTINCT p.* 
FROM products p
JOIN popular_categories pc ON p.category_id = pc.category_id
WHERE pc.sales > 10000;

-- 或者使用 EXISTS
EXPLAIN SELECT * FROM products p
WHERE EXISTS (
    SELECT 1 FROM popular_categories pc
    WHERE pc.category_id = p.category_id 
    AND pc.sales > 10000
);

-- Explain 结果:
-- type: eq_ref 或 ref
-- Extra: Using index

四、Explain 关键字段解读

1. type 字段(访问类型),从好到差:

sql

复制代码
-- 最佳到最差排序
system > const > eq_ref > ref > range > index > ALL

-- 示例解释:
const: 通过主键或唯一索引一次找到
eq_ref: 唯一索引关联查询
ref: 非唯一索引查询
range: 范围查询
index: 索引全扫描
ALL: 全表扫描

2. Extra 字段重要信息:

  • Using index: 使用覆盖索引

  • Using where: 在存储引擎后过滤

  • Using temporary: 使用临时表(需要优化)

  • Using filesort: 需要额外排序(需要优化)

  • Using join buffer: 需要使用连接缓冲区

五、实战优化案例

案例:电商订单统计查询

原始查询:

sql

复制代码
EXPLAIN 
SELECT 
    DATE(o.order_date) as day,
    COUNT(*) as total_orders,
    SUM(o.amount) as total_amount,
    AVG(o.amount) as avg_amount
FROM orders o
WHERE o.order_date BETWEEN '2024-01-01' AND '2024-12-31'
    AND o.status IN ('completed', 'shipped')
    AND o.user_id IN (
        SELECT id FROM users 
        WHERE vip_level > 1 
        AND register_time > '2023-01-01'
    )
GROUP BY DATE(o.order_date)
ORDER BY day DESC
LIMIT 30;

优化步骤:

  1. 添加索引

sql

复制代码
-- 复合索引
CREATE INDEX idx_orders_date_status_user ON orders(order_date, status, user_id, amount);
CREATE INDEX idx_users_vip_register ON users(vip_level, register_time, id);
  1. 重写查询

sql

复制代码
EXPLAIN 
SELECT 
    DATE(o.order_date) as day,
    COUNT(*) as total_orders,
    SUM(o.amount) as total_amount,
    AVG(o.amount) as avg_amount
FROM orders o
JOIN (
    SELECT id FROM users 
    WHERE vip_level > 1 
    AND register_time > '2023-01-01'
) AS vip_users ON o.user_id = vip_users.id
WHERE o.order_date >= '2024-01-01' 
    AND o.order_date < '2025-01-01'
    AND o.status IN ('completed', 'shipped')
GROUP BY DATE(o.order_date)
ORDER BY day DESC
LIMIT 30;
  1. 进一步优化(使用覆盖索引)

sql

复制代码
EXPLAIN 
SELECT 
    day,
    COUNT(*) as total_orders,
    SUM(amount) as total_amount,
    AVG(amount) as avg_amount
FROM (
    SELECT 
        DATE(o.order_date) as day,
        o.amount
    FROM orders o
    WHERE o.order_date >= '2024-01-01' 
        AND o.order_date < '2025-01-01'
        AND o.status IN ('completed', 'shipped')
        AND EXISTS (
            SELECT 1 FROM users u
            WHERE u.id = o.user_id
            AND u.vip_level > 1
            AND u.register_time > '2023-01-01'
        )
) AS order_summary
GROUP BY day
ORDER BY day DESC
LIMIT 30;

六、优化工具和技巧

1. 使用 SQL_NO_CACHE 测试

sql

复制代码
SELECT SQL_NO_CACHE * FROM large_table WHERE ...;

2. 分析索引使用情况

sql

复制代码
-- 查看索引使用统计
SHOW INDEX FROM orders;
SELECT * FROM sys.schema_index_statistics;

3. 监控慢查询

sql

复制代码
-- 开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;

-- 查看慢查询
SELECT * FROM mysql.slow_log;

4. 使用优化器提示

sql

复制代码
-- 强制使用特定索引
SELECT * FROM orders FORCE INDEX(idx_order_date) 
WHERE order_date > '2024-01-01';

-- 忽略索引
SELECT * FROM orders IGNORE INDEX(idx_order_date) 
WHERE ...;

-- 使用索引合并
SELECT * FROM orders USE INDEX(idx_user, idx_date) 
WHERE ...;

七、总结的最佳实践

  1. 索引设计原则

    • 为 WHERE、JOIN、ORDER BY、GROUP BY 的列创建索引

    • 使用复合索引时,区分度高的列在前

    • 避免过度索引,每个表建议不超过5个索引

  2. 查询编写原则

    • 使用 EXPLAIN 分析每个复杂查询

    • 小表驱动大表(小结果集驱动大结果集)

    • 避免 SELECT *,只获取需要的列

    • 合理使用批量操作,减少事务次数

  3. 监控和调整

    • 定期分析慢查询日志

    • 监控索引的使用情况

    • 根据数据变化调整索引策略

相关推荐
crossaspeed2 小时前
Java-线程池(八股)
java·开发语言
带刺的坐椅2 小时前
又一个项级的 Java Multi Agent 开源项目
java·ai·agent·solon·智能体
Voyager_42 小时前
StringRedisTemplate 和 RedisTemplate 的区别是什么?
java·spring boot
杏花春雨江南2 小时前
JavaWeb企业级项目实战:从SSH到SSM演进 + MQ/Redis/ES高可用架构落地全复盘(实战干货+避坑指南)
java·jvm·spring
lang201509282 小时前
Jackson 1.x到2.x的演进与Spring集成
数据库·sql·spring
我星期八休息2 小时前
MySQL数据可视化实战指南
数据库·人工智能·mysql·算法·信息可视化
UR的出不克2 小时前
使用 Python 爬取 Bilibili 弹幕数据并导出 Excel
java·python·excel
短剑重铸之日3 小时前
《7天学会Redis》特别篇: Redis分布式锁
java·redis·分布式·后端·缓存·redission·看门狗机制
独自破碎E3 小时前
说说Java中的反射机制
java·开发语言