📉 MySQL索引罢工事件簿:揭秘失效原因与优化起义方案

📉 MySQL索引罢工事件簿:揭秘失效原因与优化起义方案

索引小剧场:某日,程序员小明发现SQL查询突然从0.1秒暴增到5秒。索引委屈巴巴:"主人,不是我不干活,是你老给我穿小鞋啊!"


一、索引:数据库世界的超级目录

索引如同图书馆的图书目录:

  • 聚簇索引:书架按编号排序(数据即索引)
  • 二级索引:独立目录卡片(需回表查询)
  • B+树结构:多叉平衡树,3层可存2000万数据(假设每页16KB)
java 复制代码
// Java中创建索引示例(Spring Data JPA)
@Entity
@Table(indexes = @Index(columnList = "username,email", name = "idx_user_identity"))
public class User {
    @Id
    private Long id;
    private String username; // 索引列
    private String email;    // 索引列
    private Integer age;
    // Getter/Setter省略
}

二、索引罢工的五大罪状(失效场景)

1. 最左匹配原则暴动

sql 复制代码
-- 创建联合索引
CREATE INDEX idx_soldier ON army(squad, team, soldier);

-- 有效查询 ✅
SELECT * FROM army WHERE squad = 'A'; 
SELECT * FROM army WHERE squad = 'A' AND team = 2;

-- 索引罢工 ❌
SELECT * FROM army WHERE team = 2;        -- 跳过squad
SELECT * FROM army WHERE soldier = 'Tom'; -- 跳过头两列

原理:联合索引如电话簿,必须先按省→市→姓名查找,跳级查询无效


2. 隐式转换起义

java 复制代码
// Java代码中常见的类型错误
@Query("SELECT u FROM User u WHERE u.username = :name") // username是varchar
User findByUsername(@Param("name") Integer name); // 传入Integer类型!

执行SQL:

sql 复制代码
SELECT * FROM user WHERE username = 100; 
-- 类型转换导致:username列索引失效!

原理:MySQL被迫对索引列做类型转换(CAST),如同要求目录同时支持字母和数字排序


3. 函数计算抗议

sql 复制代码
-- 生日字段有索引
SELECT * FROM user WHERE YEAR(birthday) = 1990;  -- 索引失效 ❌

-- 优化方案 ✅
SELECT * FROM user 
WHERE birthday BETWEEN '1990-01-01' AND '1990-12-31';

血泪案例 :某电商平台因DATE(create_time)查询导致CPU飙升90%


4. 范围查询阻断连锁反应

sql 复制代码
CREATE INDEX idx_sales ON orders(region, amount, product);

-- 索引仅用到 region 和 amount ❌
SELECT * FROM orders 
WHERE region = 'East' 
  AND amount > 1000 
  AND product = 'Phone';

破解方案 :调整索引顺序为(region, product, amount)


5. OR引发的分裂危机

sql 复制代码
-- 即使name和age都有独立索引
SELECT * FROM user WHERE name = 'John' OR age = 30;
-- MySQL通常选择全表扫描!

优化方案:改用UNION

sql 复制代码
SELECT * FROM user WHERE name = 'John'
UNION ALL
SELECT * FROM user WHERE age = 30;

三、原理深潜:B+树为何罢工?

当发生索引失效时:

  1. 优化器计算使用索引的成本
  2. 若预计扫描超过30%数据(默认阈值)
  3. 选择全表扫描作为"更优方案"

冷知识FORCE INDEX可强制使用索引,但如同用枪逼工人干活,慎用!


四、避坑指南:四大生存法则

  1. 前缀索引策略
    ALTER TABLE article ADD INDEX idx_title(title(10));

    对长文本取前N个字符(需保证区分度>90%)

  2. 覆盖索引护盾

    sql 复制代码
    -- 建立覆盖索引
    CREATE INDEX idx_covering ON orders(user_id, status, amount);
    
    -- 查询只需索引列
    SELECT user_id, status FROM orders WHERE user_id = 1001;
  3. 索引下推(ICP)

    MySQL 5.6+ 黑科技:

    在存储引擎层提前过滤数据

  4. 索引散兵清理

    sql 复制代码
    -- 每月检查无用索引
    SELECT * FROM sys.schema_unused_indexes;

五、最佳实践:索引优化军规

场景 错误做法 正确方案
分页查询 LIMIT 1000000,10 WHERE id > last_id LIMIT
状态字段索引 建在gender列 用枚举值或放弃索引
JSON字段查询 WHERE json->'$.id'=10 生成列+索引
模糊查询 LIKE '%关键字%' 全文索引或ES
java 复制代码
// 分页优化Java实现
public Page<User> getUsers(Long lastId, int limit) {
    String sql = "SELECT * FROM user WHERE id > ? ORDER BY id ASC LIMIT ?";
    return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(), lastId, limit);
}

六、面试考点核弹区

问题1 :varchar字段传int参数为何索引失效?
:触发隐式转换→索引列计算→B+树失效→全表扫描

问题2 :如何判断索引选择性?
SELECT COUNT(DISTINCT col)/COUNT(*) FROM table

结果>0.2适合建索引

问题3:EXPLAIN中哪些信号危险?

  • type: ALL(核爆级)
  • Extra: Using filesort(排序灾难)
  • rows: 1000000(预估扫描行数)

七、终极总结:与索引和平共处原则

  1. 设计阶段

    • 优先整数字段索引
    • 联合索引遵循ASC排序原则
  2. 开发阶段

    java 复制代码
    // MyBatis防类型事故
    @Param("userId") Long userId // 而非Integer
  3. 运维阶段

    sql 复制代码
    -- 每月执行
    ANALYZE TABLE orders; 
    OPTIMIZE TABLE critical_data;

最后忠告:索引不是银弹!200万数据以下,精心设计的索引比分布式更有效;500万以上,考虑分库分表+索引的组合拳。


附录:索引健康检查清单

markdown 复制代码
- [ ] 所有SQL都通过EXPLAIN验证
- [ ] 联合索引列顺序符合查询模式
- [ ] 避免在WHERE子句中使用函数
- [ ] 定期清理冗余索引(工具:pt-duplicate-key-checker)
- [ ] 为慢查询设置监控(>0.5秒报警)

索引如忠诚的猎犬,善待它,它能在毫秒间为你寻回数据宝藏;虐待它,它会让你的数据库生不如死!🐕‍🦺💨

相关推荐
泉城老铁7 分钟前
Spring Boot整合Redis实现订单超时自动删除:从原理到实战
java·后端·架构
掘技术11 分钟前
基于Maven/Gradle多模块springBoot(spring-boot-dependencies)项目架构,适用中小型项目
java
泉城老铁13 分钟前
Spring Boot深度整合RabbitMQ:从入门到企业级实战
java·后端·rabbitmq
勤匠36 分钟前
spring shell 基础使用
java·linux·spring
Code季风1 小时前
测试驱动开发(TDD)实战:在 Spring 框架实现中践行 “红 - 绿 - 重构“ 循环
java·驱动开发·后端·spring·设计模式·springboot·tdd
hello早上好1 小时前
JPA、缓存、数据源与连接池、简介
java·mybatis
C182981825752 小时前
幻想读 通过多版本并发控制(MVCC)和间隙锁(Gap Lock)的组合也能防止幻读具体说下
mysql
想要成为祖国的花朵2 小时前
Java_Springboot技术框架讲解部分(二)
java·开发语言·spring boot·spring
vvilkim2 小时前
深入理解设计模式:原型模式(Prototype Pattern)
java·设计模式·原型模式
通域2 小时前
Mac (m1) Java 加载本地C共享库函数 .dylib 函数 Unable to load library ‘liblicense‘
java·python·macos