开始改变第六天 MySQL(2)

MySQL索引深度解析:从原理到实战优化

一、为什么需要索引?

想象一下,如果没有索引,数据库查询就像在一本没有目录的厚书中查找特定内容,只能一页页翻看。而索引就像书的目录,让我们能够快速定位到需要的数据。

二、MySQL索引底层原理

B+树索引结构

复制代码
B+树示例:
根节点: [指针1 | 键值20 | 指针2 | 键值40 | 指针3]
        /        |         \
       /         |          \
非叶节点       非叶节点       非叶节点
[5|10|15]    [25|30|35]    [45|50|55]
  /  \         /   \         /   \
叶子节点     叶子节点      叶子节点
[1,2,3,4] [6,7,8,9] [11,12,13,14]...

B+树的核心优势

  1. 所有数据都在叶子节点,查询路径长度固定
  2. 叶子节点形成双向链表,便于范围查询
  3. 非叶子节点只存索引,可以缓存更多数据
  4. 树高更低,减少磁盘I/O次数

三、索引类型详解

1. 聚簇索引(Clustered Index)

sql 复制代码
-- InnoDB中,主键就是聚簇索引
CREATE TABLE users (
    id INT PRIMARY KEY,           -- 聚簇索引
    name VARCHAR(50),
    age INT,
    email VARCHAR(100)
);

特点:

  • 叶子节点存储完整行数据
  • 每张表只能有一个聚簇索引
  • 物理存储顺序与索引顺序一致

2. 二级索引(Secondary Index)

sql 复制代码
-- 创建二级索引
CREATE INDEX idx_name ON users(name);
CREATE INDEX idx_age ON users(age);

特点:

  • 叶子节点存储主键值
  • 查询时需要"回表"操作
  • 每张表可以有多个二级索引

四、索引创建实战指南

单列索引 vs 复合索引

sql 复制代码
-- 单列索引:适合单个条件查询
CREATE INDEX idx_email ON users(email);

-- 复合索引:适合多条件查询
CREATE INDEX idx_name_age ON users(name, age);
CREATE INDEX idx_name_age_city ON users(name, age, city);

复合索引的最左前缀原则

sql 复制代码
-- 索引: (name, age, city)

-- 使用索引的情况:
SELECT * FROM users WHERE name = '张三';
SELECT * FROM users WHERE name = '张三' AND age = 25;
SELECT * FROM users WHERE name = '张三' AND age = 25 AND city = '北京';

-- 不使用索引的情况:
SELECT * FROM users WHERE age = 25;                    -- 违反最左前缀
SELECT * FROM users WHERE age = 25 AND city = '北京';   -- 违反最左前缀
SELECT * FROM users WHERE city = '北京';                -- 违反最左前缀

索引选择性原则

sql 复制代码
-- 计算索引选择性
SELECT 
    COUNT(DISTINCT gender) / COUNT(*) as gender_selectivity,    -- 选择性低
    COUNT(DISTINCT email) / COUNT(*) as email_selectivity       -- 选择性高
FROM users;

-- 选择性 > 0.1 的列适合建立索引

五、回表查询与覆盖索引

回表查询过程

sql 复制代码
-- 假设有二级索引 idx_name
SELECT * FROM users WHERE name = '张三';

-- 执行流程:
-- 1. 在idx_name索引树中找到'张三'对应的主键id
-- 2. 用主键id到聚簇索引中查找完整数据行
-- 3. 返回完整数据
-- 这个过程就是"回表"

覆盖索引优化

sql 复制代码
-- 创建覆盖索引
CREATE INDEX idx_name_age ON users(name, age);

-- 覆盖索引查询:直接从索引获取数据,避免回表
SELECT name, age FROM users WHERE name = '张三';

-- 检查是否使用覆盖索引
EXPLAIN SELECT name, age FROM users WHERE name = '张三';
-- 在Extra列看到: Using index

覆盖索引扩展应用

sql 复制代码
-- 创建包含更多字段的覆盖索引
CREATE INDEX idx_name_age_city ON users(name, age, city);

-- 以下查询都可以使用覆盖索引
SELECT name FROM users WHERE name = '张三';
SELECT name, age FROM users WHERE name = '张三';
SELECT name, age, city FROM users WHERE name = '张三';
SELECT COUNT(*) FROM users WHERE name = '张三';

六、索引失效的完整场景分析

1. 违反最左前缀原则

sql 复制代码
-- 索引: (name, age, city)
SELECT * FROM users WHERE age = 25;                    -- ❌ 失效
SELECT * FROM users WHERE city = '北京';                -- ❌ 失效
SELECT * FROM users WHERE name = '张三' AND city = '北京'; -- ✅ 部分使用

2. 在索引列上使用函数或计算

sql 复制代码
-- 索引: (create_time)
SELECT * FROM users WHERE YEAR(create_time) = 2023;    -- ❌ 失效
SELECT * FROM users WHERE create_time + 1 > NOW();     -- ❌ 失效

-- 优化方案:
SELECT * FROM users WHERE create_time >= '2023-01-01' AND create_time < '2024-01-01'; -- ✅

3. 类型转换导致失效

sql 复制代码
-- 假设phone是varchar类型
SELECT * FROM users WHERE phone = 13800138000;         -- ❌ 失效(数字转字符串)
SELECT * FROM users WHERE phone = '13800138000';       -- ✅ 使用索引

-- 假设id是int类型
SELECT * FROM users WHERE id = '100';                  -- ✅ 使用索引(字符串转数字)

4. LIKE模糊查询

sql 复制代码
-- 索引: (name)
SELECT * FROM users WHERE name LIKE '%张三%';           -- ❌ 失效
SELECT * FROM users WHERE name LIKE '%张三';            -- ❌ 失效
SELECT * FROM users WHERE name LIKE '张三%';            -- ✅ 使用索引

5. 使用不等于条件

sql 复制代码
SELECT * FROM users WHERE status != 1;                 -- ❌ 可能全表扫描
SELECT * FROM users WHERE status <> 1;                 -- ❌ 可能全表扫描

-- 优化方案:
SELECT * FROM users WHERE status IN (0, 2, 3);         -- ✅ 使用索引
SELECT * FROM users WHERE status > 1 OR status < 1;    -- ✅ 使用索引

6. OR条件处理

sql 复制代码
-- 假设name有索引,age无索引
SELECT * FROM users WHERE name = '张三' OR age = 25;   -- ❌ 索引失效

-- 优化方案:
SELECT * FROM users WHERE name = '张三'
UNION ALL
SELECT * FROM users WHERE age = 25 AND name != '张三'; -- ✅ 分别使用索引

7. 范围查询后的索引列失效

sql 复制代码
-- 索引: (name, age, city)
SELECT * FROM users WHERE name = '张三' AND age > 20 AND city = '北京';
-- ✅ name使用索引,✅ age使用索引,❌ city无法使用索引

-- 优化方案:调整索引顺序或拆分为多个查询
CREATE INDEX idx_name_city_age ON users(name, city, age);

七、索引设计最佳实践

1. 选择合适的索引列

sql 复制代码
-- 优先为以下列创建索引:
-- 1. WHERE条件中的列
-- 2. JOIN关联条件的列  
-- 3. ORDER BY排序的列
-- 4. GROUP BY分组的列

2. 复合索引设计技巧

sql 复制代码
-- 设计原则:等值查询列在前,范围查询列在后
CREATE INDEX idx_status_create_time ON orders(status, create_time);

-- 查询:等值 + 范围
SELECT * FROM orders WHERE status = 1 AND create_time > '2023-01-01';

3. 前缀索引优化

sql 复制代码
-- 对于长文本列,使用前缀索引
CREATE INDEX idx_content ON articles(content(100));  -- 只索引前100个字符

-- 计算合适的前缀长度
SELECT 
    COUNT(DISTINCT LEFT(content, 50)) / COUNT(*) as selectivity_50,
    COUNT(DISTINCT LEFT(content, 100)) / COUNT(*) as selectivity_100,
    COUNT(DISTINCT LEFT(content, 200)) / COUNT(*) as selectivity_200
FROM articles;

八、索引性能监控与分析

使用EXPLAIN分析查询

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

-- 重点关注:
-- type: const > ref > range > index > ALL
-- key: 实际使用的索引
-- rows: 预估扫描行数
-- Extra: Using index(覆盖索引), Using where, Using filesort, Using temporary

索引使用情况统计

sql 复制代码
-- 查看索引使用频率
SELECT 
    OBJECT_SCHEMA,
    OBJECT_NAME,
    INDEX_NAME,
    COUNT_READ,
    COUNT_FETCH
FROM performance_schema.table_io_waits_summary_by_index_usage
ORDER BY COUNT_READ DESC;

-- 查找未使用的索引
SELECT * FROM sys.schema_unused_indexes;

九、实际案例分析

电商系统索引设计

sql 复制代码
-- 订单表索引设计
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id BIGINT,
    status TINYINT,
    create_time DATETIME,
    total_amount DECIMAL(10,2),
    -- 其他字段...
    INDEX idx_user_status (user_id, status),
    INDEX idx_create_time (create_time),
    INDEX idx_status_create (status, create_time)
);

-- 商品表索引设计  
CREATE TABLE products (
    id BIGINT PRIMARY KEY,
    category_id INT,
    price DECIMAL(10,2),
    name VARCHAR(200),
    -- 其他字段...
    INDEX idx_category_price (category_id, price),
    INDEX idx_name (name)
);

总结

索引是数据库性能优化的核心,正确的索引设计可以提升查询性能几个数量级。记住以下要点:

  1. 理解B+树原理:知道索引如何工作才能更好使用
  2. 掌握最左前缀原则:复合索引设计的基石
  3. 善用覆盖索引:避免回表,提升性能
  4. 警惕索引失效:避免常见的索引使用陷阱
  5. 持续监控优化:根据实际查询模式调整索引策略

索引不是越多越好,合适的索引才是最好的索引。在实际应用中,要根据具体的业务场景和查询模式来制定索引策略。

相关推荐
码界奇点7 小时前
Java 开发日记MySQL 与 Redis 双写一致性策略挑战与实战解析
java·redis·sql·mysql·java-ee
小杨的全栈之路7 小时前
MySQL性能优化全攻略:从原理到实践
mysql
晓py7 小时前
理解 MySQL 架构:从连接到存储的全景视图
数据库·mysql·架构
DemonAvenger7 小时前
Redis分布式锁:实现原理深度解析与实战案例分析
数据库·redis·性能优化
NineData8 小时前
NineData社区版 V4.6.0 正式发布!SQL 窗口新增4个数据源,新增支持OceanBase等多条数据复制和对比链路
数据库·sql·dba
IT果果日记8 小时前
给DataX配置加密的方法
大数据·数据库·后端
小白学鸿蒙8 小时前
鸿蒙数据库表中的数据如何导出为Excel存到系统下载目录
数据库·excel·harmonyos
WKP94188 小时前
mysql的事务、锁以及MVCC
数据库·mysql
那我掉的头发算什么8 小时前
【数据库】增删改查 高阶(超级详细)保姆级教学
java·数据库·数据仓库·sql·mysql·性能优化·数据库架构